#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) }
作为参数传递“移动”对象的成员是否安全?在这种情况下,emplace似乎有效:地图具有预期的关键.
解决方法
有趣.出于错综复杂的原因,我认为这是安全的. (为了记录,我也认为这是非常糟糕的风格 – 明确的副本在这里没有任何成本,因为它将被移动到地图中.)
首先,实际的函数调用不是问题. std :: move只将a转换为rvalue引用,而rvalue引用只是引用; 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(const T1& x,T2 &&y) : first(std::forward(x)),second(std::forward(y)) { }
按照20.3.2(6-8).
那么,这样安全吗?有用的是,std :: pair的定义有些细节,包括内存布局.特别是,它说明了这一点
T1 first; T2 second;
按顺序排列,所以首先在第二个之前初始化.这意味着在您的特定情况下,字符串将在移走之前被复制,并且您是安全的.
但是,如果你这样做,反过来说:
m.emplace(std::move(A.s),A); // huh?
…然后你会得到有趣的效果.