杂项
RAII & 智能指针
RAII
RAII: Resource acquisition is initialization
将资源管理交给其本身的思想。
尽量使用对象的生命周期来管理资源,初始化时调用构造函数进行资源分配,离开作用域时自动调用析构函数释放资源。而不是自己来管理资源的分配与释放。
智能指针
所以很自然想到智能指针:
template<class T>
class auto_ptr{
public:
auto_ptr(T *p=0):ptr(p) {}
~auto_ptr() { delete ptr; }
T* operator->() const { return ptr; }
T& operator *() const { return *ptr; }
private:
T* ptr;
}
这种手搓的是会出问题的。比如拷贝构造后,两个智能指针对象指向同一块内存,会导致这块内存被析构函数释放两次。
标准库智能指针
头文件:#include <memory>
auto_ptr
和手搓的基本一样。代码不是很复杂的情况下用这个就行。
unique_ptr
独占所有权的指针。就是说,unique_ptr 指向的内存只能被它一个人占有。同一时间内只会有一个 unique_ptr 指向同一块内存。
这建立在你完全使用 unique_ptr 提供的接口的情况之上。也就是说完全有办法让两个 unique_ptr 指向同一块内存。
C++14 以后,使用 std::make_unique
来创建 unique_ptr。
特点是:
- 不允许拷贝构造
- 支持移动语义
using int_ptr=std::unique_ptr<int>;
int *ptr = new int(5);
int_ptr p(ptr);
int_ptr p_ = std::make_unique<int>(5); // In c++14
cout<< *p << endl; // 5
int_ptr q = p; // you can not clone unique_ptr!!!
int_ptr r(std::move(p)); // move semantic
cout<< *r << endl; // 5
int_ptr s(ptr); // this will cause double free!!!
shared_ptr
含引用计数的指针。这样就可以实现多个指针指向同一对象了。
当指向某内存的最后一个 shared_ptr 被释放以后才会释放这块内存。
特点:
- 有复制构造函数,支持共享所有权
- 支持移动语义
- 避免多次释放内存
using int_ptr=std::shared_ptr<int>;
int *ptr = new int(5);
int_ptr p(ptr);
int_ptr p_ = std::make_shared<int>(5); // In c++ 14
int_ptr q = p; // ok :)
但还是无法解决循环引用的问题。要结合 weak_ptr 完成。
weak_ptr
专门用来解决循环引用问题的智能指针,真气派。
只能配合 shared_ptr 使用。简单来说就是一种不参与引用计数的 shared_ptr。
所以 weak_ptr 可以从从一个 shared_ptr 或另一个 weak_ptr 对象构造。并且 weak_ptr 完全不负责资源管理,没有 RAII 的特性。
青春版 goto
有时候用 goto 会使得分支结构变得简单,如下:
if(check) {
goto bad;
}
work();
return;
bad:
exception();
但是 goto 不让用,这时候可以用 do_while(0) 代替:
do {
if(check) {
break;
}
work();
return;
}while(0);
exception();
容器扩容中的移动
tldr: STL 容器的元素类需要移动构造,扩容时触发。
STL 容器在扩容时的步骤:
- 申请一块更大的新空间(不同编译器扩容倍率不同,GCC 似乎是 2 倍扩容)
- 逐个调用旧空间中每个元素的移动构造,移动到新的空间
- 回收旧空间
所以,STL 扩容并非字面意思上的“扩建”,而是住不下了就搬家。 带来的问题 & 需要注意的点:
- 需要保证元素类有移动构造或者拷贝构造。最好是移动构造,更符合“搬家”的语义。
- 迭代器、指针会失效。因为还在指向扩容前的空间。
when const
tldr: 在类中使用 const 方法表达逻辑不可变性,而不是用 const 成员变量 即用接口保证不变性,不要让成员变量阻断类型系统的优化路径。
刚学 c++ 时,有一个流传甚广的 “golden rule”:
Use const whenever possible —— Effective C++ Item 3
很容易写出 const 成员变量。这会导致对象无法移动、赋值,失去值语义。这实际上违反了现代 C++ 的核心哲学。
作为 solution,请使用接口表达逻辑不可变性:
class A{
public:
int get_id() const { return id; }
private:
int id; // Better not add const here
}
reference
Herb Sutter:
❝ Do not put
const
on member variables unless you really want to break assignment, move, and containers. ❞
—— “Elements of Modern C++ Style”
Bjarne Stroustrup:
❝
const
is a great idea for local variables and references in functions, but it’s almost always a bad idea on class members. ❞
—— C++ Core Guidelines
Google C++ Style Guide:
Avoid const data members. They make classes harder to use with standard containers and cause surprising behavior.