std::vector::emplace_back and std::move

40

使用std::vector::emplace_backstd::move是否有任何优势?或者只是多余的,因为std::vector::emplace_back会进行原地构造?

需要澄清的情况:

std::vector<std::string> bar;

首先:

bar.emplace_back(std::move(std::string("some_string")));

第二点:

std::string str("some_string");
bar.emplace_back(std::move(str));

第三点:

bar.emplace_back(std::move("some_string"));

3
第三个操作至少是无意义的。那个字面字符串是常量,因此无法移动。 - H. Guijt
4个回答

41
在第二个版本中,有一个优点。当使用std::move时,调用emplace_back将调用std::string的移动构造函数,这可能会节省一次复制操作(只要该字符串不存储在SSO缓冲区中)。请注意,在这种情况下,这与push_back本质上是相同的。

在第一个版本中,std::move是不必要的,因为字符串已经是一个prvalue。

在第三个版本中,std::move是无关紧要的,因为字符串字面量不能被移动。

最简单和最有效的方法是:

bar.emplace_back("some_string");

这需要避免不必要的std::string构建,因为字面值被完美地转发到构造函数。


1
请问“在这种情况下与push_back相同”可以澄清一下吗?具体是哪种情况?如果参数被移动,那么emplace_back是否总是可以替换为push_back - Felix Dombek
1
在第一种和第二种情况下, emplace_back 可以替换为 push_back - sp2danny
如果将 emplace_back 替换为 push_back,那么这不会导致复制而不是移动吗? - Hugo Burd
1
@HugoBurd 晚了点回复,但是没有,push_back 有一个移动版本。 - cebola

9
< p > emplace_back 调用类似于以下内容的代码

new (data+size) T(std::forward<Args>(args)...);

如果 args 是非右值引用的基本 std::string,则表达式将编译为:
new (data+size) std::string(str); //str is lvalue - calls std::string::string(const string& rhs)

意味着会发生复制构造函数。 但是,如果在 str 上使用 std::move,则代码将编译为
new (data+size) std::string(str); //str is r-value reference, calls std::string::string(string&& rhs)

所以进行了移动语义。这是一个巨大的性能提升。
请注意,str 是一个左值(lvalue),它有一个名称,因此为了从它创建右值引用(r-value-reference),必须使用 std::move

在这个例子中:

vec.emplace_back("some literal"); 

代码将编译为:
new (data+size) std::string("literal"); //calls std::string::string(const char*);

因此不需要临时变量。

第三个例子是无意义的。您不能移动字面值。


6
emplace_back 的整个思想是为了摆脱复制和移动操作。只需要将 std::string 的输入参数传递给 emplace_back 方法。一个 std::string 对象将在 emplace_back 方法内部构造。
bar.emplace_back("some_string");

如果您已经有一个字符串,使用 std::move 是有意义的。在 emplace_back 中将通过从 str 移动数据来构造 std::string 对象。

std::string str("some_string");
bar.emplace_back(std::move(str));

1
在第二种情况下这样做是有意义的。考虑以下代码:
int main()
{
    std::vector<std::string> bar;
    std::string str("some_string");
    bar.emplace_back(std::move(str)); str.clear();
    // bar.emplace_back(str);
    std::cout << str << std::endl;
}

如果你将注释改为上一行,你会发现最终会有两个“some_string”副本(一个在bar中,另一个在str中)。因此,它确实改变了某些内容。
否则,第一个是移动临时变量,第三个是移动常量字符串字面值。它什么也不做。

1
你的代码(不包括注释)中把结果字符串 str 移动后再传递给 cout,这会导致 str 悬空,对我来说似乎很危险。 - Wolfgang
@Wolfgang 谢谢,非常好的观点。应该先调用 str.clear()。已更新答案。 - Ami Tavory

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