条款41 对于移动成本低且总是被拷贝的可拷贝形参,考虑按值传递
class Widget {
public:
void addName(std::string newName) { //接受左值或右值;移动它
names.push_back(std::move(newName));
}
…
}
由于是值传递,所以生成了一个副本,移动它没有后果。
在C++98中,无论调用者传递什么,形参addName()
都是拷贝出来,但是在C++11中,只有在左值实参时,才是拷贝来的;对于右值,使用移动构造。
- 按值传递应该仅考虑那些移动开销小的形参。当移动的开下较低,额外的一次移动才能被开发者接受。
- 应该只对总是被拷贝的形参考虑按值传递。
使用通过赋值拷贝一个形参进行按值传递的函数的额外开销,取决于传递的类型,左值和右值的比例,这个类型是否需要动态分配内存,以及,如果需要分配内存的话,赋值操作符的具体实现,还有赋值目标占的内存至少要跟赋值源占的内存一样大。对于std::string
来说,开销还取决于实现是否使用了小字符串优化(SSO),如果是,那么要赋值的值是否匹配SSO缓冲区。
条款42 考虑使用置入代替插入
如果有一个存放std::string
的容器,通过插入函数添加新元素的时候,传入的元素类型应该是std::string
:
std::vector<std::string> vs; //std::string的容器
vs.push_back("xyzzy"); //添加字符串字面量
此处试图给容器添加一个字符串字面量,并不是一个std::string
在
vs.push_back("xyzzy");
中,编译器看到实参类型(const char[6]
)和形参类型(std::string
的引用)之间不匹配。所以从字符串字面量创建一个std::string
类型的临时对象消除不匹配(类似这样):
vs.push_back(std::string("xyzzy")); //创建临时std::string,把它传给push_back
- 一个
std::string
的临时对象从字面量"xyzzy"
被创建。这个对象没有名字,我们可以称为temp
。temp
的构造是第一次std::string
构造。因为是临时变量,所以temp
是右值。 temp
被传递给push_back
的右值重载函数,绑定到右值引用形参x
。在std::vector
的内存中一个x
的副本被创建。这次构造——也是第二次构造——在std::vector
内部真正创建一个对象。(将x
副本拷贝到std::vector
内部的构造函数是移动构造函数,因为x
在它被拷贝前被转换为一个右值,成为右值引用。- 在
push_back
返回之后,temp
立刻被销毁,调用了一次std::string
的析构函数。
使用**emplace_back**
函数
完美转发,没有临时变量产生,而是直接在容器内构造一个对象:
vs.emplace_back("xyzzy"); //直接用“xyzzy”在vs内构造std::string
插入函数接收对象去插入;而置入函数接收对象的构造函数接受的实参去插入:
置入函数支持如下的操作:
vs.emplace_back(50, 'x'); //插入由50个“x”组成的一个std::string
满足下列条件时,置入操作会优于插入操作:
- 值是通过构造函数添加到容器,而不是直接赋值;
- 传递的实参类型与容器的初始化类型不同。因为容器不需要构造临时对象;
- 容器不拒绝重复项作为新值。如果值已经存在,那么置入操作取消,创建的节点被销毁,意味着构造和析构的开销被浪费了。
拷贝初始化不允许使用 explicit 构造函数
在C++中:
std::regex r1 = nullptr; //错误!不能编译
std::regex r2(nullptr); //可以编译
使用等号的(r1)初始化是拷贝初始化,使用小括号(r2)初始化是直接初始化。
using regex = basic_regex<char>;
explicit basic_regex(const char* ptr,flag_type flags); //定义 (1)explicit构造函数
basic_regex(const basic_regex& right); //定义 (2)拷贝构造函数
拷贝初始化不被允许使用**explicit**
构造函数(即没法调用相应类的explicit
拷贝构造函数):对于r1
,使用赋值运算符定义变量时将调用拷贝构造函数定义 (2)
,其形参类型为basic_regex&
。因此nullptr
首先需要隐式装换为basic_regex
。而根据定义 (1)
中的explicit
,这样的隐式转换不被允许,从而产生编译时期的报错。对于直接初始化,编译器会自动选择与提供的参数最匹配的构造函数,即定义 (1)
。就是初始化r1
不能编译,而初始化r2
可以编译的原因。