Effective Modern C++ 笔记 - 走进现代C++

说起知名的特性,C++11/14有一大堆可以吹的东西,auto,智能指针,移动语意,lambda,并发——每个都是如此的重要,这章将覆盖这些内容。 精通这些特性是必要的,但是成为高效率的现代C++程序员也要求一系列小步骤。 从C++98移步C++11/14遇到的每个细节问题都会在本章得到答复。 应该在创建对象时用{}而不是()吗?为什么alias声明比typedef好?constexpr和const有什么不同?常量成员函数和线程安全有什么关系?这个列表越列越多。 这章将会逐个回答这些问题。

7. 小括号和大括号

在类中成员直接赋值时用大括号{}就对了, 通用!

调用类构造函数时,(){}行为是不同的

当类构造函数有std::initializer_list时, 用{}总是优先匹配初始化列表. 而()则不会主动使用初始化列表.
当调用无参数的构造函数时, 由于使用()会造成歧义, 应使用{}

1
2
3
4
std::vector<int> v1(10, 20);  // 生成10个元素, 值都是20
std::vector<int> v2{10, 20}; // 用std::initializer_list, 生成2个元素, 分别是10和20
std::vector<int> v3{}; // 调用空构造
std::vector<int> v4({}); // 用std::initializer_list

注意下面的obj1obj2, 区别和上面一样, 对std::initializer_list有不同的优先级.
标准库里的std::make_uniquestd::make_shared实现使用小括号

1
2
3
4
5
6
template<typename T, typename... Ts>
void doSomeWork(Ts&&...params)
{
T obj1{std::forward<Ts>(params)...};
T obj2(std::forward<Ts>(params)...);
}

8. 优先用nullptr代替NULL

9. 优先用using代替typedef

using可以这么用

1
2
template<typename T> using MyList = std::list<T, MyAlloc<T>>;
MyList<int> l1;

typedef实现上面的效果要这样

1
2
3
4
5
6
template<typename T> struct MyList {
typedef std::list<T, MyAlloc<T>> type;
};
MyList<int>::type l2;
// 如果要在模板中使用, 由于编译器不确定是否存在某些偏特化里定义了type的成员, 所以必须指明typename
typename MyList<T>::type list;

10. 优先用enum class

11. 优先用deleted functions

防止拷贝, 之前的套路是把未定义的拷贝构造函数和赋值函数放到private

1
2
3
4
5
6
class Widget {
public:
private:
Widget(const Widget&);
Widget& operator=(const Widget&);
}

C++11以后应该这么做

1
2
3
4
5
class Widget {
public:
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
}

除了在类中使用, deleted functions也可以:

  1. 明确拒绝指定参数类型的函数
  2. 明确拒绝指定参数类型的特化
    1
    2
    3
    4
    5
    6
    bool isLucky(int);
    bool isLucky(char) = delete;

    template<typename T> void processPointer(T* ptr);
    template<> void processPointer<void>(void*) = delete;

12. 用上override声明覆盖函数

加上override后, 方便编译器找出错误
另外提到, C++11加入了左值专用方法和右值专用方法的定义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Panel
{
public:
using Components = std::vector<Widget>;

// 左值专用, 返回引用
Components& getChildren() &
{return children;}

// 右值专用, 返回右值引用
Components&& getChildren() &&
{return std::move(children);}

private:
Components children;
};

13. 优先使用const_iterator

  1. 优先使用const_iterator
  2. 优先使用非成员版本的begin, end, cbegin, cend, rbegin, …

14. 如果函数不会抛出异常, 就声明noexcept

15. 尽量使用constexpr

  • constexpr声明的对象在编译期就确定
  • constexpr声明的函数, 当参数全部是编译期确定时, 函数结果也能在编译期得到.
  • constexpr函数如果能在编译期计算, 则在编译期计算, 否则就在运行期计算.
  • constexpr对象和函数应用范围更广, 这也意味着如果后悔了, 想去掉constexpr声明, 可能会造成大范围的编译问题.
    1
    2
    3
    4
    5
    6
    7
    8
    constexpr int pow(int base, int exp) noexcept;

    constexpr auto numConds = 5;
    std::array<int, pow(3, numConds)> results; // 编译期计算

    auto base = readFromDB("base");
    auto exp = readFromDB("exponent");
    auto baseToExp = pow(base, exp); // 在运行期计算

16. const成员函数应该线程安全

17. 了解特殊成员函数

C++11加入了move构造函数和move赋值操作.
编译器只在下列情形下生成生成move构造和move操作

  • 没有自定义拷贝
  • 没有自定义move
  • 没有自定义析构

同样,如果自定义了move, 也不再自动生成拷贝操作.
模板构造函数不会阻止生成拷贝和move
如果需要默认操作的特殊成员函数,用=default声明.

1
2
3
4
5
6
7
8
class Base {
public:
virtual ~Base() = default;
Base(Base&&) = default;
Base& operator=(Base&&) = default;
Base(const Base&) = default;
Base& operator=(const Base&) = default;
}