条款41 对于移动成本低且总是被拷贝的可拷贝形参,考虑按值传递

  1. class Widget {
  2. public:
  3. void addName(std::string newName) { //接受左值或右值;移动它
  4. names.push_back(std::move(newName));
  5. }
  6. }

由于是值传递,所以生成了一个副本,移动它没有后果。

在C++98中,无论调用者传递什么,形参addName()都是拷贝出来,但是在C++11中,只有在左值实参时,才是拷贝来的;对于右值,使用移动构造。

  • 按值传递应该仅考虑那些移动开销小的形参。当移动的开下较低,额外的一次移动才能被开发者接受。
  • 应该只对总是被拷贝的形参考虑按值传递。

使用通过赋值拷贝一个形参进行按值传递的函数的额外开销,取决于传递的类型,左值和右值的比例,这个类型是否需要动态分配内存,以及,如果需要分配内存的话,赋值操作符的具体实现,还有赋值目标占的内存至少要跟赋值源占的内存一样大。对于std::string来说,开销还取决于实现是否使用了小字符串优化(SSO),如果是,那么要赋值的值是否匹配SSO缓冲区。

条款42 考虑使用置入代替插入

如果有一个存放std::string的容器,通过插入函数添加新元素的时候,传入的元素类型应该是std::string

  1. std::vector<std::string> vs; //std::string的容器
  2. vs.push_back("xyzzy"); //添加字符串字面量

此处试图给容器添加一个字符串字面量,并不是一个std::string

  1. vs.push_back("xyzzy");

中,编译器看到实参类型(const char[6])和形参类型(std::string的引用)之间不匹配。所以从字符串字面量创建一个std::string类型的临时对象消除不匹配(类似这样):

  1. vs.push_back(std::string("xyzzy")); //创建临时std::string,把它传给push_back
  1. 一个std::string的临时对象从字面量"xyzzy"被创建。这个对象没有名字,我们可以称为temptemp的构造是第一次std::string构造。因为是临时变量,所以temp是右值。
  2. temp被传递给push_back的右值重载函数,绑定到右值引用形参x。在std::vector的内存中一个x的副本被创建。这次构造——也是第二次构造——在std::vector内部真正创建一个对象。(将x副本拷贝到std::vector内部的构造函数是移动构造函数,因为x在它被拷贝前被转换为一个右值,成为右值引用。
  3. push_back返回之后,temp立刻被销毁,调用了一次std::string的析构函数。

使用**emplace_back**函数

完美转发,没有临时变量产生,而是直接在容器内构造一个对象

  1. vs.emplace_back("xyzzy"); //直接用“xyzzy”在vs内构造std::string

插入函数接收对象去插入;而置入函数接收对象的构造函数接受的实参去插入:

置入函数支持如下的操作:

  1. vs.emplace_back(50, 'x'); //插入由50个“x”组成的一个std::string

满足下列条件时,置入操作会优于插入操作:

  • 值是通过构造函数添加到容器,而不是直接赋值
  • 传递的实参类型与容器的初始化类型不同。因为容器不需要构造临时对象;
  • 容器不拒绝重复项作为新值。如果值已经存在,那么置入操作取消,创建的节点被销毁,意味着构造和析构的开销被浪费了。

拷贝初始化不允许使用 explicit 构造函数

在C++中:

  1. std::regex r1 = nullptr; //错误!不能编译
  2. std::regex r2(nullptr); //可以编译

使用等号的(r1)初始化是拷贝初始化,使用小括号(r2)初始化是直接初始化

  1. using regex = basic_regex<char>;
  2. explicit basic_regex(const char* ptr,flag_type flags); //定义 (1)explicit构造函数
  3. basic_regex(const basic_regex& right); //定义 (2)拷贝构造函数

拷贝初始化不被允许使用**explicit**构造函数(即没法调用相应类的explicit拷贝构造函数):对于r1,使用赋值运算符定义变量时将调用拷贝构造函数定义 (2),其形参类型为basic_regex&。因此nullptr首先需要隐式装换为basic_regex。而根据定义 (1)中的explicit,这样的隐式转换不被允许,从而产生编译时期的报错。对于直接初始化,编译器会自动选择与提供的参数最匹配的构造函数,即定义 (1)。就是初始化r1不能编译,而初始化r2可以编译的原因。

image.png