条款十三
优先使用const_iterator
对于iterator与const_iterator
它们是两个不同的类型,而不是说一个是另一个加上/除去const修饰符,故static_cast、remove_const等是做不到其间的相互转换的。
因此,只要能加cosnt,那就加上cosnt,即你不需要改变容器成员的时候尽量用const_iterator。
这里要提到一点,const容器的begin与end便是返回const_iterator,故非成员函数cbegin与cend其实只是调用了std::begin与std::end,不同的是他们使用c传入容器的const版本来调用。
条款十四
为不抛出异常的函数加上noexcept
noexcept函数被调用的时候栈是不需要为之保持可开解状态的。因为你强调了不会抛出异常,故不存在逆向析构的必要,因此程序会更加优化、迅速。
例如移动操作,若移动操作可能抛出异常,那么std::move会选择把容器内的移动改为使用复制以防止移动操作进行到一半时抛出异常导致对象无法复原
包括其他类似的依靠复制的函数,也是这样的,比如swap
swap( type1 a[], type2 b[]) noexcept(noexcept(swap(a,b)))
让交换数组的swap异常规格与普通的交换type1与type2的swap函数一致,此处noexcept亦用作异常规格判断的函数。
但是不要为了加上noexcept而去采用更复杂的函数结构,当然,如果你能确定某些没有noexcept的函数并不会抛出异常,那么你也可以在noexcept函数之中使用它们。
条款十五
只要有可能使用constexpr,那么就使用
constexpr用于变量时说明变量值编译时便已知
constexpr亦可以作用于构造函数用以构造一个cosntexpr对象,其他的成员函数同理。作用于cosntructor便说明可以构造一个用于constexpr环境的对象。
对于一个constexpr修饰返回值的函数,当这个函数接受constexpr参数并用于需要constexpr返回值的地方时,此函数会返回constexpr值,若是接受了非constexpr参数,则和普通函数无异,也无法用于需要constexpr返回值的地方。
另外在c++11中,constexpr函数默认是const类型,且不可以是void返回型,因为在c++11中void不是一个字面型别。并且,在c++11之中,constexpr函数只能有一条return语句,不能含有其他语句。这些限制在c++14中均被解除,c++14之中constexpr函数可以包含循环等多条语句,但通常不允许执行IO语句。这也就意味着设置器也可以定义成constexpr:
constexpr void setX(int x) noexcept
{ this.x = x; }
也就意味着你甚至可以更改一个constexpr对象的成员。
总而言之,constexpr意味着函数可以用在比普通函数更加广泛的范围之中,故尽可能得使用constexpr。但同时,constexpr是作用于对象的接口之上的,故一旦你觉得你的constexpr定义不合理,带来的是毁灭性的灾难(所有你将这个接口用于constexpr的代码都失效了,而且是真的失效了,你很难用一个非constexpr对象来替代constexpr对象),故使用constexpr的条件便是强大的可靠性,你需要确定这个对象是可以作为constexpr对象的。-
条款十六
保证const成员函数的线程安全
条款十七
理解特种成员的生成机制
除此外,模板函数不会阻止默认构造函数以外的其他特种构造函数的合成。
条款十八
使用unique_ptr管理具有专属所有权的资源
unique_ptr ≈ 一个裸指针的大小(若没有自定义析构器的话)。
unique_ptr可以转换为一个shared_ptr所以可以用作工厂函数的返回值。
unique_ptr的自定义析构器需要加入到声明中。
(所以如果你的析构器有太多的状态的话就会搞得unique_ptr变得很大)
对于一个unique_ptr(T[], delete[])//管理数组的unique_ptr
一般别这么用,使用array等容器会更好,除非你需要用在一个返回数组的API上。
条款十九
使用shared_ptr管理共享所有权的资源
shared_ptr的大小为两倍于裸指针:
指向引用计数块的指针 + 指向资源的指针
引用计数块包含:引用计数、弱计数(weak_ptr)、删除器、分配器
效能影响:1、引用计数块是必须要进行动态分配的。
2、引用计数递增或递减必须为原子计数以保障线程安全性。
计数块/控制块的创建:
1、make_shared函数会创建一个控制块
2、使用unique_ptr创建shared_ptr会创建一个控制块
3、使用裸指针创建shared_ptr会创建一个控制块
4、直接使用shared_ptr创建另一个不会引起控制块创建
故任何使用裸指针引起超过一个控制块被创建的代码都是会出错的。
使用this创建一个shared_ptr的安全方法
class widget : public enable_shared_from_this
{
//...
static vector processwidgets;
auto process()
{
processwidgets.emplace_back(shared_from_this());
}
//...
};
如果不继承enable_shared_from_this,那么当类实例被外部创建的sahred_ptr管理时,外部的shared_ptr便不能得知内部已经有shared_ptr管理此类实例。
最后,不要尝试使用sahred_ptr去管理原生数组,用容器来代替原生数组,毕竟shared_ptr没有operato[]。
条款二十
需要共享但是可能空悬时使用weak_ptr
1、weak_ptr不可以使用*取值,这防止了shared_ptr使用*取不到值时抛出异常的情况。
2、weak_ptr.lock()返回一个shared_ptr当,引用计数为0时这个shared_ptr为空,即可以通过检测lock返回值是否为空来及检测是否管理资源。
3、用weak_ptr初始化shared_ptr若weak_ptr失效了则会抛出异常。
4、考虑一中情况,A、B、C三个数据结构,A和C共享B的所有权,A和C均使用shared_ptr指向B,为了方便,B也需要一个指向A的智能指针。那么就有三种选择:
1、裸指针,此时B无法检测到A是否析构,若A被析构,而B无意中需要提领(*)A的值,则会出错。
2、shared_ptr,A和B互相保有对方的shared_ptr也就意味着A和B使得对方的引用计数为1,但事实上外部已经没有方法访问这两个它们各自保有的shared_ptr了,即形成了内存泄漏。另外因为释放A需要释放A中指向B的指针,而要释放指向B的智能指针又要释放A,形成死循环。
3、使用weak_ptr,对于1,weak_ptr可以检测指涉对象是否还存在,对于2,weak_ptr不影响引用计数,不会阻止,也不会引起析构。