条款42.考虑置入而非插入

考虑置入而非插入

如果你有个容器,持有一些,比如说,std::string类型的对象,那么似乎合乎逻辑的做法是经由某个插入函数来向其添加新元素,而你传递给函数的元素类型将是std::string

说合乎逻辑倒也确实合乎逻辑,但却不一定合乎事实。

std::vector<std::string> vs;

vs.push_back("xyzzy");				//添加字符串字面量

这里,容器持有的是std::string类型对象,但是你手头上有的是个字符串字面量。字符串字面量不是std::string,也就是说,传递给push_back的实参并非容器持有之物的类型。

std::vectorpush_back针对左值和右值给出了不同的重载版本。

template<class T,
		 class Allocator = allocator<T>>
class vector{
public:
    ...
    void push_back(const T& x);		//插入左值
    void push_back(T&& x);			//插入右值
};

在下面的调用语句中:

vs.push_back("xyzzy");

编译器会看到实参类型与push_backstd::string的引用类型)接受的形参类型之间的不匹配。而解决之道就是通过生成代码以从字符串字面量出发创建std::string类型的临时对象,并将该零食对象传递给push_back。换言之,它们把这句调用看作下面这样:

vs.push_back(std::string("xyzzy"));	//创建std::string类型的临时对象,并将其传递给push_back

这段代码并不高效

因为

  • 从字符串字面量xyzzy出发,创建std::string类型的临时对象。该对象没有名字,我们称其为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:可以使传入的任何实参在std::vector内构造一个std::string。不会涉及任何临时对象

vs.emplace_back("xyzzy");			//直接从"xyzzy"出发在vs内构造std::string类型对象

emplace_back使用了完美转发,所以只要你没有遭遇完美转发的限制,就可以通过emplace_back传递任意类型的任意数量和任意组合的实参。例如,如果你想通过std::string的那个接受一个字符及重复计数的构造函数虫子啊版本来创建一个std::string类型对象,下面的代码就可以做到。

vs.emplace_back(50, 'x');		//插入一个由50个'x'字符组成的string类型对象

emplace_back可用于任何支持push_back的标准容器。相似的,所有支持push_front的标准容器也支持emplace_front。还有,任何支持插入操作(亦即,除了std::forward_liststd::array以外的所有标准容器)都支持置入操作

使得置入函数超越插入函数成为可能的,是置入函数更加灵活的接口。插入函数接受的是待插入对象,而置入函数接受的则是待插入对象的构造函数实参。这一区别就让置入函数得以避免临时对象的创建和析构,但插入函数就无法避免。

因为具备容器所持有物的实参可以被传递给一个置入函数(该实参数会引发函数执行拷贝或者移动构造),所以即使在插入函数并不要求创建临时对象的情况下,也可以使用置入函数。在那种情况下,插入函数和置入函数本质上做的是同一件事

例如,给出下面的std::string类型对象

std::string queenOfDisco("Donna Summer");

下面两个调用语句都成立,并且对于容器来说净效果相同。

vs.push_back(queenOfDisco);

vs.emplace_back(queenOfDisco);

这样一来,置入函数就能做到插入函数所能做到的一切。有时,他们可以比后者做的更高效一些。而且,至少在理论上,它们不会比后者效率更低。

从理论上来说,理论和实践没有区别:但从实践上来说,理论和实践是有区别的。从标准库的当前实现上来看,在有些情况下,正如预期的那样,置入的性能优于插入,但是,不幸的是,还是存在插入函数运行得更快的情况。后面一种情况不太容易表现,因为具体来说,取决于传递的实参类型,使用的容器种类,请求插入或置入的容器位置,所持有类型构造函数的异常安全性等等。这里适用一般的性能调优建议:欲确定置入或插入哪个运行得更快,需要对两者实施基准测试。

如果下列情况都成立,那么置入将几乎比插入更高效。

  • 欲添加的值是以构造而非赋值方式加入容器。

要点速记

  • 从原理上说,置入函数应该有时比对应的插入函数高效,而且不应该有更低效的可能
  • 从实践上说,置入函数在以下几个前提成立时,极有可能会运行得更快:(1)待添加的值是以构造而非赋值方式加入容器;(2)传递的实参类型与容器持有物的类型不同;(3)容器不会由于存在重复值而拒绝待添加的值
  • 置入函数可能会执行在插入函数中被拒绝的类型转换
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值