作为参数传递正在移动的对象的成员是否安全

时间:2014-12-03 08:21:07

标签: c++ c++11 move-semantics

#include <iostream>
#include <string>
#include <map>

struct A {
    int n { 42 };
    std::string s { "ciao" };
};

int main() {
    A a;
    std::map<std::string, A> m;
    std::cout << "a.s: " << a.s << std::endl; // print: "a.s: ciao"
    m.emplace(a.s, std::move(a)); // a.s is a member of a, moved in the same line
    std::cout << "in map: " << m.count("ciao") << std::endl; // print: "in map: 1"
    std::cout << "a.s: " << a.s << std::endl; // print: "a.s: " (as expected, it has been moved)
}

作为一个参与者传递一个&#34;移动&#34;是否安全?宾语?在这种情况下,emplace似乎有效:地图具有预期的密钥。

2 个答案:

答案 0 :(得分:3)

有趣。出于错综复杂的原因,我认为这是安全的。 (为了记录,我也认为它非常糟糕 - 明确的副本在这里没有任何成本,因为它将被移动到地图中。)

首先,实际的函数调用不是问题。 std::move仅将a转换为右值引用,而右值引用只是引用; a不会马上移动。 emplace_back将其参数转发给std::pair<std::string, A>的构造函数,这就是事情变得有趣的地方。

那么,使用了std::pair的构造函数?它有很多,但有两个是相关的:

pair(const T1& x, const T2& y);
template<class U, class V> pair(U&& x, U&&y);

(参见标准中的20.3.2),其中T1T2std::pair的模板参数。根据13.3,我们最后使用U == const T1&V == T2,后者具有直观意义(否则进入std::pair实际上是不可能的)。这给我们留下了

形式的构造函数
pair(const T1& x, T2 &&y) : first(std::forward(x)), second(std::forward(y)) { }

根据20.3.2(6-8)。

那么,这样安全吗?有用的是,std::pair被详细定义,包括内存布局。特别是,它声明

T1 first;
T2 second;

按此顺序排列,因此first将在second之前初始化。这意味着在您的特定情况下,字符串将在移走之前被复制,并且您是安全的。

但是,如果你反过来这样做:

m.emplace(std::move(A.s), A); // huh?

...然后你会得到有趣的效果。

答案 1 :(得分:3)

调用m.emplace(a.s, std::move(a));时会发生的情况是您将a.s的l值引用和a的r值引用传递给emplace函数。这是否安全取决于该功能的实现。如果它首先从第二个参数中删除了内容,然后尝试使用第一个参数,那么您可能会遇到问题。如果它首先使用第一个参数,然后从第二个参数中撕下胆量,没问题。由于这是一个标准的库函数,你不能依赖于实现,所以我们不能断定代码是安全的。

为了确保安全,您可以确保在字符串的内容移出a之前创建字符串的副本。

m.emplace(std::string(a.s), std::move(a));

现在,您将r值引用传递给a.s的临时副本,并将a的r值引用传递给emplace函数。因此,在这种情况下它是安全的,因为std::map::emplace采用了一组通用引用。如果你对一个按值获取第二个参数的函数也这样做,那就不安全了,因为可以在a.s的副本之前调用移动构造函数(因为函数参数的求值顺序是未指定)。知道这一点,这段代码可能太聪明了,无法维护。

因此,最清晰的代码如下:

auto key = a.s;
m.emplace(std::move(key), std::move(a));
相关问题