#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似乎有效:地图具有预期的密钥。
答案 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),其中T1
和T2
是std::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));