条款41.针对可复制的形参,在移动成本低并且一定会被复制的前提下,考虑按值传递
- 对于可以复制,在移动成本低的一定会被复制的形参而言,按值传递可能会和按引用传递有差不多的效率,而且不需要写更多代码
- 通过构造复制形参的成本,可能比赋值赋值形参高很多
- 按值传递肯定会有切片问题,所以这里值传递不适合基类
// 三种传值的方式
// 通过重载的方式传递,需要维护两个函数
// 成本:左值=一次复制 右值=一次移动
class Widget{
public:
void addName(const std::string& newName)
{ names.push_back(newName); }
void addName(std::string&& newName)
{ names.push_back(newName); }
}
// 使用万能引用,需要写到头文件
// 成本:左值=一次复制 右值=一次移动
class Widget{
public:
template<typename T>
void addName(T&& newName)
{ names.push_back(std::forward<T>(newName)); }
}
// 按值传递
// 成本:无论是左值还是右值都存在一次额外移动
class Widget{
public:
void addName(std::string newName)
{ names.push_back(std::move(newName)); }
}
条款42.考虑置入而非插入
使用emplace_back而非push_back
使用push_back时
vs.push_back("xyzzy")
// 将被看做
vs.push_back(std::string("xyzzy"))
首先会产生临时对象,临时对象传递给push_back的右值重载版本(一次移动),然后在std::vector中创建一个新的对象(第二次构造),最后临时对象被析构。
而使用emplace_back时实际上传入的是构造参数。
如果在以下情况度成立的情况,置入效率一定更高:
- 添加的值是以构造而非赋值的方式加入容器
- 传递的实参类型和容器的持有类型不同
- 容器不太可能因为重复情况而拒绝新值,例如set,如果拒绝则浪费一次构造和析构
与资源管理一起用的情况
在使用shared_ptr的情况下使用push_back可能成本会超过预期,而如果使用emplace_back可能导致异常发生的时候产生泄漏。
我们任何时候都应该先定义shared_ptr然后传入容器中:
std::shared_ptr<Widget> spw(new Widget, killWidget);
ptrs.push_back(std::move(spw))
std::shared_ptr<Widget> spw(new Widget, killWidget)
ptrs.emplace_ptr(std::move(spw))
传入空指针的陷阱
regexes.emplace_back(nullptr)//不会报错
regexes.push_back()//报错
因为emplace_back传入的是构造参数,所以实际上是允许空指针的,一些用explicit拒绝的类型,在这种情况下也被允许传入,可能会发生问题。