如何解决移动构造函数和移动重载赋值运算符的问题? 输出2_main()输出3_main输出4
主要由 fredoverflow(用户 237K 代表)在他的两个答案中解释的所有内容
但是在实现移动构造函数和重载移动赋值运算符(OMAO
)(我在整个问题中都使用这些简短形式)时,我遇到了一些问题,我将放在此处。
用户 Greg Hewgill(拥有 826K 代表的用户)还有另一个答案
https://stackoverflow.com/a/3106136/11862989his
我引用他的话,
假设你 有一个返回实体对象的函数,然后是一个普通的 C++ 编译器将为multiply()的结果创建一个临时对象, 调用复制构造函数初始化r,然后析构 临时返回值。 C++0x 中的移动语义允许“移动 构造函数”被调用以通过复制其内容来初始化 r,以及 然后丢弃临时值而不必破坏它。
我也会参考这个问题。
好的,我要开始了
代码
.cpp
#include"34_3.h"
#include<iostream>
#include<conio.h>
#include<cstring>
A::A() // O arg ctor
{
std::cout<<"0 arg constructor\n";
p=0;
s=nullptr;
}
A::A(int k1,const char *str) // 2 arg ctor
{
std::cout<<"2 arg constructor\n";
p=k1;
s=new char[strlen(str)+1];
strcpy(s,str);
}
A::A(const A &a) // copy ctor
{
std::cout<<"copy constructor\n";
p=a.p;
s=new char[strlen(a.s)+1];
strcpy(s,a.s);
}
A::A(A &&a) // Move ctor
{
std::cout<<"Move constructor\n";
p=a.p;
s=new char[strlen(a.s)+1];
strcpy(s,a.s);
a.s=nullptr;
}
A& A::operator=(const A &a) // Overloaded assignement opeator `OAO`
{
std::cout<<"overloade= operator\n";
p=a.p;
s=new char[strlen(a.s)+1];
strcpy(s,a.s);
return *this;
}
A& A::operator=(A &&a) // `OMAO`
{
std::cout<<"Move overloade = operator\n";
p=a.p;
s=new char[strlen(a.s)+1];
strcpy(s,a.s);
a.s=nullptr;
return *this;
}
A::~A() // Dctor
{
delete []s;
std::cout<<"Destructor\n";
}
void A::display()
{
std::cout<<p<<" "<<s<<"\n";
}
.h
#ifndef header
#define header
struct A
{
private:
int p;
char *s;
public:
A(); // 0 arg ctor
A(int,const char*); // 2 arg ctor
A(const A&); // copy ctor
A(A&&); // Move ctor
A& operator=(const A&); // `OAO`
A& operator=(A&&); // `OMAO`
~A(); // dctor
void display(void);
};
#endif
我把几个主要的函数和它们的输出放在这里,这样我就可以很容易地讨论这个问题。
1_main
A make_A();
int main()
{
A a1=make_A();
a1.display();
}
A make_A()
{
A a(2,"bonapart");
return a;
}
输出
2 arg constructor
2 bonapart
Destructor
- 为什么它不执行 Move 构造函数,但是如果我注释掉了 .cpp 文件中的 Move 构造函数定义和 .h 文件中的声明,那么它会给出错误
[Error] no matching function for call to 'A::A(A)'
,如果我使用这个A a1=std::move(make_A());
然后 Move 构造函数调用,那么为什么会发生这种情况? - 为什么 make_A() 函数中对象
a
的析构函数没有运行?
2_main()
A make_A();
int main()
{
A a1;
a1=make_A();
a1.display();
}
A make_A()
{
A a(2,"bonapart");
return a;
}
输出
0 arg ctor
2 arg ctor
Move overloade = operator
copy ctor
Dctor
Dctor
2 bonapart
Dctor
- 现在这里复制构造函数和析构函数为由于从 Move 重载 = 运算符函数返回 *this 而创建的临时对象运行。根据 Greg Hewgill 语句
C++ 0x
允许调用 Move 构造函数以通过复制其内容来初始化,然后丢弃临时值而不必破坏它。我正在使用C++11
,但仍然通过创建临时对象、复制构造函数来完成初始化。 - 我不知道第二个析构函数正在运行哪个对象?
3_main
fredoverflow(用户 237K 代表)保留了 Move 重载运算符 A&
的返回类型,但我认为这是错误的。
A make_A();
int main()
{
A a1,a2;
a2=a1=make_A();
a1.display();
a2.display();
}
A make_A()
{
A a(2,"bonapart");
return a;
}
输出
[Error] prototype for 'A& A::operator=(A&&)' does not match any in class 'A'
所以我觉得返回类型应该是 A&&
或 A
但 A&&
也会给出错误 [ERROR] can't bind a lvalue to a&&
所以返回类型必须是 A
,对吗?
4
在移动构造函数和移动重载 = 操作符中我使用了 a.s=nullptr;
这个语句总是在移动语义中使用 fredoverflow(user) 解释了类似“现在源不再拥有它的对象”但我没有得到它.因为如果我不写这个语句仍然没有问题一切正常。请解释这一点
解决方法
您的班级 A
有几个问题:
-
您的赋值运算符不处理自赋值和泄漏:
A& A::operator=(const A& a) { std::cout<<"overload operator=\n"; if (this != &a) { p = a.p; delete[] s; s = new char[strlen(a.s) + 1]; strcpy(s,a.s); } return *this; }
-
你的动作不是移动而是复制:
A::A(A&& a) : p(a.p),s(a.s)
{
a.s = nullptr;
std::cout << "Move constructor\n";
}
A& A::operator=(A&& a)
{
std::cout << "Move overload operator=\n";
if (this != &a) {
p = a.p;
delete [] s;
s = a.s;
a.s = nullptr;
}
return *this;
}
现在,关于
A make_A()
{
A a(2,"bonapart"); // Constructor
return a;
}
由于潜在的复制省略(NRVO),有几种情况
(gcc 的标志为 -fno-elide-constructors
来控制)
如果 NRVO 适用,则 a
是“就地”构造,因此不会发生额外的破坏/移动;
否则有一个移动构造函数和 a
的销毁。
A make_A()
{
A a(2,"bonapart"); // #2 ctor(int const char*)
return a; // #3 move (elided with NRVO)
} // #4 destruction of a,(elided with NRVO)
int main()
{
A a1; // #1: default ctor
a1 = // #5: Move assignment (done after make_A)
make_A(); // #6: destructor of temporary create by make_A
a1.display();
} // #8: destructor of a1
使用 NRVO
default ctor
ctor(int const char*)
move assignment
destructor
display
destructor
没有 NRVO (-fno-elide-constructors
)
default ctor
ctor(int const char*)
move ctor
destructor
move assignment
destructor
display
destructor
为了
A a1,a2;
a2 = a1 = make_A();
a1 = make_A();
使用移动分配。
a2 = (a1 = make_A())
使用复制赋值作为移动赋值返回(正确)A&
4
在 Move 构造函数和 Move 重载 = 操作符中,我使用了 a.s=nullptr;
这个语句总是在 Move 语义中使用 fredoverflow(user) 解释了诸如“现在源不再拥有它的对象”之类的东西,但我没有得到它。因为如果我不写这个语句仍然没有问题一切正常。请解释这一点
你的问题是你复制而不是移动。
如果您执行 s = a.s;
而不是复制
s = new char[strlen(a.s) + 1];
strcpy(s,a.s);
然后 this->s
和 a.s
将指向相同的数据,并且 this
和 a
将释放其析构函数中的(相同)内存 -> 双重释放错误.
a.s = nullptr;
会解决这个问题。
1_main:由于复制省略而未执行移动构造函数 交换完成后观察到额外的析构函数,临时对象被销毁。
2_main:与调用移动运算符的 1_main 相同的观察
3_main:由于您使用的是低版本的编译器,因此可以看到该错误。可能需要指定 -std=c++11
4:a.s=nullptr
不是移动的情况,因为您正在分配新的记忆并进行某种复制。例如
A::A(A &&a) // Move ctor
{
std::cout<<"Move constructor\n";
p=a.p;
s=a.s;
a.s=nullptr;
a.p=0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。