我有一个关于 C++ 中移动语义的问题。看这个例子:
class Buck {
public:
Buck(std::vector<int> param) : data{param} {}
std::vector<int> data;
};
int main() {
std::vector<int> A{1,2,3,4,5};
Buck b{std::move(A)};
std::cout << A.size() << std::endl;
return 0;
}
这输出0,这意味着
A
已经被移动到data
的b
变量中。
现在,最大的问题 - 为什么这有效?
据我所知,
A
被移动到 param
构造函数的参数 Buck
中(因为我强制它成为带有 std::move()
的右值,并且调用了 std::vector
中的移动构造函数)。 param
本身应该是构造函数中的左值。那么,如果 data{param}
是左值,std::vector
究竟如何调用 param
的移动构造函数?
A
被移动到param
然后param
被复制到data
。您可以使用以下方法进行测试:
#include <iostream>
struct Data{
Data() {
std::cout << "init\n";
}
Data(Data const&) {
std::cout << "copy\n";
}
Data(Data&&) {
std::cout << "move\n";
}
};
struct Buck {
Buck(Data param): data{param} {} // copy
Data data;
};
int main() {
Data a; // init
Buck b{std::move(a)}; // move
}
init
move
copy
如果你想移动,你总是需要明确地转换为一个
rvalue
(例如通过std::move
):
struct Buck {
Buck(Data param): data{std::move(param)} {} // move
Data data;
};
int main() {
Data a; // init
Buck b{std::move(a)}; // move
}
init
move
move
如果想单次移动数据成员,可以在构造函数中取引用:
struct Buck {
Buck(Data&& param): data{std::move(param)} {} // move
Data data;
};
int main() {
Data a; // init
Buck b{std::move(a)}; // call-by-reference
}
init
move
注意: 您仍然需要在成员初始化中显式
rvalue
-cast!如果您不使用它,您将复制一份:
struct Buck {
Buck(Data&& param): data{param} {} // copy!!!
Data data;
};
int main() {
Data a; // init
Buck b{std::move(a)}; // call-by-reference
}
init
copy
std::move(a)
与static_cast<Data&&>(a)
相同。当您使用 Data&&
类型的变量时,它被处理为 Data&
直到您在表达式中明确地将其转换为 Data&&
。声明一个变量为Data&&
只对如何初始化它很重要,而不是如何使用它!
按值调用很有用,如果想要一个易于实现的复制和移动支持:
struct Buck {
// param can be copied or moved, then it is moved to data
Buck(Data param): data{std::move(param)} {}
Data data;
};
int main() {
Data a; // init
std::cout << "----\n";
Buck b{a}; // copy
std::cout << "----\n";
Buck c{std::move(a)}; // move
}
init
----
copy
move
----
move
move
如果你想要最高性能,你可以通过同时实现复制和移动构造函数来避免第二次移动:
struct Buck {
Buck(Data const& param): data{param} {} // copy
Buck(Data&& param): data{std::move(param)} {} // move
Data data;
};
int main() {
Data a; // init
std::cout << "----\n";
Buck b{a}; // call-by-lvalue-reference
std::cout << "----\n";
Buck c{std::move(a)}; // call-by-rvalue-reference
}
init
----
copy
----
move