C++

Effective Modern C++(4): Modern C++(2)

Posted by keys961 on June 5, 2022

1. 若不抛异常则使用noexcept

好处:

  1. 更好展示函数不会抛出异常

  2. 由于展开异常调用栈对代码生成有影响,因此若不抛出异常,标注noexecpt后,编译器能尽可能优化

  3. 某些操作中有用

    • move操作:例如std::vector::push_back,若对象move函数为noexcept,那么通过调用std::move_if_noexcept,将拷贝操作优化为move操作

    • 内存释放

    • 析构函数

  4. 可定义函数视情况noexcept

    • 例如std::swap中数组和std::pair的版本
  5. 大多数函数是异常中立(可能抛也可能不抛),而非noexcept

虽然noexcept可以带来优化,但正确性更重要,若有可能出现异常,则不能加noexcept

2. 尽量使用constexpr

语法:constexpr <expression>

意义:表明expression的值是一定是编译期可知(且一定不变)的。

  • 该值可以放到需要编译期常量的地方(例如模板参数)

  • const不如constexpr,只能保证值不变(例如临时const变量)

constexpr函数:

  • 若传参编译期可知,则结果在编译期可知(即编译时就被计算),可用于放到需要编译期常量的地方

  • 若传参编译期不可知,则等同普通函数,若放到需要编译期常量的地方,则编译错误

  • 函数可以是构造函数、getter和setter,若传参编译期可知,那么结果(包括类中的成员)也编译期可知

  • constexpr是对象和函数接口的一部分

    • 相当于宣称“我能被用在要求常量表达式的地方”

3. 让const成员函数线程安全

因为const函数的意义在于调用时,不会变更成员内部值,返回时一个相同的值。

但是有时候确实必要修改很小一部分内部值,此时可以用mutable修饰这一部分值。

但修改后,多线程下,若不保证线程安全,则行为未定义,从而打破const成员函数的语义。

因此,尽量让const函数线程安全,除非不会并发访问。

4. 特殊成员函数的生成

特殊成员函数:

  • 默认构造函数

  • 析构函数

  • 拷贝构造函数:T(const T&)

  • 拷贝赋值运算:T& T::operator=(const T&)

  • 移动构造函数:T(T&&)

  • 移动赋值运算:T& T::operator=(T&&)

新增的移动成员函数,默认特点:

  • 对非静态成员逐个move

  • 同样影响父类部分

  • 若成员不支持移动,则会拷贝

总结特殊成员函数的生成和处理规则:

  1. 默认构造函数:仅类没有声明构造函数时生成

  2. 析构函数:仅类没有声明构造函数时生成,且为noexcept

  3. 拷贝构造函数:

    • 没有定义拷贝构造时生成

      • 若声明了移动操作,它默认是delete
    • 逐个拷贝非静态成员

  4. 拷贝赋值运算:

    • 没有定义拷贝赋值时生成

      • 若声明了移动操作,它默认是delete
    • 逐个拷贝非静态成员

  5. 移动操作(构造+赋值运算):

    • 当没定义拷贝、移动和析构操作时生成

    • 逐个移动(调用std::move)非静态成员,若不支持移动,则回退为拷贝

  6. 成员函数的模板不影响生成特殊成员函数的规则