我一直试图了解移动构造函数场景背后发生了什么,但我脑子里想不到什么。
struct A {
int s;
A() : s(10) {}
A(const A& o) : s(o.s) { std::cout << "copy contructor" << std::endl;}
A(A&& o) : s(std::move(o.s)) {std::cout <<"move constructor" << std::endl;}
};
struct B {
std::string s;
B() : s("max") {;}
B(const B& o) : s(o.s) {std::cout <<"copy contructor" << std::endl;}
B(B&& o) : s(std::move(o.s)) {std::cout <<"move contructor" << std::endl;}
};
int main() {
A a1;
std::cout << " Before move " << " " << a1.s << std::endl;
A a2 = std::move(a1);
std::cout << " After move" << " " << a1.s << std::endl;
B b1;
std::cout << " Before move" << " " << b1.s << std::endl;
B b2 = std::move(b1);
std::cout << " After move" << " "<< b1.s << std::endl;
return 0;
}
在一个对象上调用std :: move,应该明确表示尽管该对象在移动后仍然存在,但我们很乐意清空“。
The output is
Before move 10
move constructor
After move 10
Before move max
move constructor
After move ""
我有两个问题:
1)移动struct B的构造函数“窃取”资源并将b1.s留空(“”)。这对我来说非常有意义,为什么这不会发生在a1.s.它看起来像一个副本,而不是一个举动。
2)如果我从struct B的移动构造函数中移除std :: move,
B(B&& o) : s(o.s) {std::cout <<"move contructor" << std::endl;}
我想应该调用一个拷贝构造函数。现在输出是:
Before move max
move constructor
After move max
这对我来说有点意义。我完全理解“最大移动后的资源”。现在它看起来像一个副本,但没有应该使用复制构造函数调用打印出来的“复制构造函数”的痕迹。
我心中肯定缺少某些东西,有人可以解释为什么吗? THX
答案 0 :(得分:3)
从基本类型移动与副本完全相同。当您处理包含动态分配资源的对象时,移动才非常重要。例如,std::string
将动态分配其字符串内容,这些内容将从原始std::string
移动到新内容。
由于此处std::move
:
B b2 = std::move(b1);
表达式std::move(b1)
是一个右值,因此将选择移动构造函数来构造b2
。改变移动构造函数的作用不会改变它。你有改变的是移动构造函数与复制构造函数的行为相同(除了它打印“使用移动构造函数”的事实)。由于您已将s(std::move(o.s))
更改为s(o.s)
,因此只会复制内部字符串。
答案 1 :(得分:0)
我对这一点的理解也很模糊。因此,在以下情况下,我尝试根据我的理解进一步分解步骤:对于持有资源(int *)的类,在rvalue引用上进行全能交换
这就是目标(我放弃了您会在跟踪中看到的大多数格式...)
int main() {
int a {1}; cout << "a:" << a << "_@_" << (&a) << "\n";
int b {2}; cout << "b:" << b << "_@_" << (&b) << "\n";
C ca {"ca",&a}; C cb {"cb",&b};
ca.p();cb.p();
swap(ca,cb);
ca.p();cb.p();
}
现在使用右值引用的交换是“经典”
void swap(C& a, C& b){
C tmp {"tmp",static_cast<C&&>(a)};
a = static_cast<C&&>(b);
b = static_cast<C&&>(tmp);
}
所以,这就是我对类的外观的理解
class C{
private : string _id; int* _pi;
public:
//default construct
C(string id):
_id(id), _pi(nullptr)
{ cout << _id << ":C()_default_build\n"; }
//construct from int
C(string id, int* pi):
_id(id), _pi(new int(*pi))
{ cout << _id << ":C(" << (*_pi) << "_@_" << _pi << ")_build_from_int\n"; }
//contruct from other object, by copy
C(string id, const C& other):
_id(id), _pi(new int(*(other._pi)))
{ cout << _id << ":C(" << (*_pi) << "_@_" << _pi << ")_build_as_copy_from(" << other._id << ")\n"; }
//contruct from other object, by move
C(string id, C&& other):
_id(id), _pi( other._pi )
{
other._pi = nullptr;
cout << _id << ":C(" << (*_pi) << "_@_" << _pi << ")_build_by_move_from(" << other._id << ")\n";
}
//destructor
~C() {
cout << "~~ destroy " << _id;
if (_pi){
cout << " holding " << (*_pi) << " at " << _pi ;
delete _pi; _pi = nullptr;
}
else
cout << " __empty__ ";
cout << " ~~\n";
}
//move assignment
C& operator=(C&& other) noexcept{
if (this != &other) {
_pi = other._pi;
other._pi = nullptr;
cout << "## " << _id << " steals " << _pi << " from " << other._id << " ##\n";
}
return *this;
}
//display
void p() {
cout << ">> " << _id;
if (_pi)
cout << " points to " << (*_pi) << " at " << _pi;
else
cout << "__empty__";
cout << " <<\n";
}
};
现在,开火! ...痕迹
/**trace
--- simulate rvalue references ---
a:1_@_0x7ffd6a1af45c
b:2_@_0x7ffd6a1af458
ca:C(1_@_0x558eabb0c280)_build_from_int
cb:C(2_@_0x558eabb0c2a0)_build_from_int
>> ca points to 1 at 0x558eabb0c280 <<
>> cb points to 2 at 0x558eabb0c2a0 <<
tmp:C(1_@_0x558eabb0c280)_build_by_move_from(ca)
## ca steals 0x558eabb0c2a0 from cb ##
## cb steals 0x558eabb0c280 from tmp ##
~~ destroy tmp __empty__ ~~
>> ca points to 2 at 0x558eabb0c2a0 <<
>> cb points to 1 at 0x558eabb0c280 <<
~~ destroy cb holding 1 at 0x558eabb0c280 ~~
~~ destroy ca holding 2 at 0x558eabb0c2a0 ~~
*/
好的..对于这个例子,它可以工作...这是正确的答案吗?