std::vector<std::string>的push_back与emplace_back比较

7

类似的问题在这里这里都有讨论,但我认为我们没有得到一个清晰的答案,因此我重新提出这个问题。

C++11标准有两种向vector中添加新元素的方法,它们是std::vector::push_backstd::vector::emplace_back

它们之间的区别在于std::vector::emplace_back会直接就地构造对象,而std::vector::push_back则基本上是将对象(或原始类型)复制或移动到向量末尾。

因此,对于将原始类型添加到std::vector中,std::vector::push_back看起来是更好的选择。例如:

std::vector<int> vVec;
vVec.push_back(10);
int iVar 30;
vVec.push_back(iVar);

但是当我们谈论对象时,情况并不那么清晰。以std::string向量为例,如果我想添加一个字面字符串,我应该使用std::vector::emplace_back,而当我想复制或移动字符串对象时,我应该使用std::vector::push_back吗?
为了更清楚地说明我的问题,让我们看几个场景:
第一个场景:
std::vector<string> vVec;
std::string sTest("1st scenario");
vVec.push_back(sTest);  
vVec.emplace_back(sTest); 

push_back将sTest的副本与vVec末尾的新元素相关联,而emplace_backvVec末尾创建字符串对象,并将sTest的内容复制到其中。

在这种情况下,哪个更有效率或者无所谓?

第二种场景:

std::vector<string> vVec;
std::string sTest1("2st scenario");
std::string sTest2("2st scenario");
vVec.push_back(move(sTest1));   
vVec.emplace_back(move(sTest2)); 

Push_back方法是具备移动语义的,但emplace_back呢?在这种情况下哪个更有效率呢?

第三种情况:

std::vector<string> vVec;
std::string sTest("3st scenario");
vVec.push_back(sTest.substr(4,4));
vVec.emplace_back(sTest.substr(4,4));

由于std::string::substr返回另一个std::string,因此我们有了第二种情况的相同情况?
结论
如果std::vector::emplace_back比std::vector::push_back更高效,只有涉及字面值(原地构造)时才这样做,那么它真的可以被证明是可用的吗?如果可以,请给我一些例子。
需要注意的重要事项是,std::vector::emplace_back是在c++11中实现的,而不是在c++0x中实现的,因此我认为它存在的理由是充分的。

2
为什么您要先构造一个字符串,然后再将其移动到 emplace_back 中呢?抱歉,但我发现您的示例更加混乱而不是清晰。据我所知,emplace_back 可以在推入实例之前为您节省构造实例的步骤,因此我认为您的比较并不公平。 - 463035818_is_not_a_number
emplace_back 不会像 push_back 一样保存我构造实例,而是在原地进行,换句话说,它使用了自定义的构造函数。实际上,我想知道 emplace_back 是否完全像 push_back 一样,并且它是否具有 push_back 没有的某些优势。 - TheArchitect
“基本上就是复制对象(或原始数据类型)。” 原始数据类型也是对象。 任何您可以存储在std :: vector中的类型都是对象类型。 - Jonathan Wakely
当然,你必须在某个时候构造一个实例,但是使用emplace_back,你不需要在容器外部构造实例,然后再将其推入。在大多数示例中,在将其插入向量之前确实会创建一个实例,因此我认为比较push_backemplace_back对于这些情况是毫无意义的。 - 463035818_is_not_a_number
3个回答

13

在您的三个场景中,这两个函数都会在情况1中调用一个复制构造函数,在情况2或3中调用一个移动构造函数,因此它们并没有太大区别。

但如果您想构造一个由10个'x'字符组成的字符串呢?在这种情况下,您有以下几个选择:

vVec.push_back(std::string(10, 'x'));
vVec.emplace_back(10, 'x');

在这种情况下,push_back 调用一个自定义的 string 构造函数和移动构造函数,但是 emplace_back 直接调用自定义的 string 构造函数,避免了调用移动构造函数。

std::string 的移动构造函数可能并不是什么大问题,但是当对象没有有效的移动构造函数时,emplace 函数可以节省时间,并且即使类类型由于某些原因而被删除了移动构造函数,也可以使用它。 (好吧,如果你删除了移动和复制构造函数,std::vector 不会高兴,但其他容器可能还好。)

注:原文中的 "emplace" 在翻译时应该翻译为 "emplace函数" 或者 "emplace成员函数",因为在上下文中它是指特定容器的成员函数。

如果删除移动和复制构造函数,vector 就没问题了,只是你将无法调用 push_back - Nir Friedman
你还可以提到,对于没有标记为“nothrow”的移动操作类型,如果需要调整大小,则push_back将复制而不是移动现有元素到新缓冲区中。因此,除非真正关心强异常保证,否则应始终优先使用emplace_back - Corristo

13

首先,让我们澄清一下:

emplace 系列函数接受 构造函数的参数,而不是对象本身。

然后它使用这些参数在原地构造对象,在这个过程中,它永远不会构造一个临时对象,然后将其复制或移动到容器中。

也就是说,用与所需对象相同类型的对象作为参数正是拷贝和移动构造所做的事情,这就是为什么在你的例子中,它们调用同样的构造函数:它们都使用已经构造好的 string 对象作为参数。

emplacepush 的完全不同之处在于当 emplace 被调用时,传递给它的参数不是对象本身,而是用于构造对象的构造函数参数:emplace 不需要构造一个临时对象,然后再将其复制到容器中。

std::vector<std::string> strvec;
strvec.emplace_back("abc")  //calls string(const char*)

strvec.push_back("abc")          //is equivalent to...
strvec.push_back(string("abc"))  //which calls string(const char*) then string(string&&)

2

我花了一些时间才真正理解使用std::vector::emplace的优势,就像aschepler所说的那样。

我发现更好的使用场景是当我们有自己的类在构造时接收一些数据。

为了更清楚,假设我们有:

  1. 一个MyObject向量
  2. MyObject需要接收3个参数来构造
  3. get1stElem(),get2ndElem()和get3rdElem()函数提供构造MyObject实例所需的元素

然后我们可以有这样一行代码:

vVec.emplace(get1stElem(), get2ndElem(), get3rdElem());

那么,std::vector::emplace会比std::vector::push_back更高效地在原地构建MyObject。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接