Move 构造函数和 Move 重载赋值运算符有问题吗?
problems with Move constructor and Move overloaded assignment operator?
fredoverflow(用户 237K Rep.)在他的两个答案中解释了大部分内容
但是在实现 Move 构造函数和重载 Move Assignment 运算符时(OMAO
)(我在整个问题中都使用这些简短形式)我遇到了一些问题,我将把它们放在这里。
用户 Greg Hewgill(拥有 826K Rep. 的用户)也有另一个答案
我引用他的话,
Suppose you
have a function that returns a substantial object then an ordinary C++
compiler will create a temporary object for the result of multiply(),
call the copy constructor to initialize r, and then destruct the
temporary return value. Move semantics in C++0x allow the "move
constructor" to be called to initialize r by copying its contents, and
then discard the temporary value without having to destruct it.
我也会提到这个问题。
好的我开始
代码
.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());
然后移动构造函数调用,那么为什么会这样?
- 为什么 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
- 现在这里复制构造函数和析构函数为由于 return *this 来自 Move 重载 = 运算符函数而创建的临时对象运行。根据 Greg Hewgill 的说法,
C++ 0x
允许调用 Move 构造函数来通过复制它的内容来初始化,然后丢弃临时值而不必破坏它。我正在使用 C++11
但仍然通过创建临时对象、复制构造函数来完成初始化。
- 我不知道第二个析构函数是哪个对象 运行?
3_main
fredoverflow(用户 237K Rep.)保留了 return 类型的 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'
所以我觉得 return 类型应该是 A&&
或 A
但是 A&&
也会报错 [ERROR] can't bind a lvalue to a&&
所以 return 类型必须是 A
,对吗?
4
在 Move 构造函数和 Move 重载 = 运算符中,我使用了 a.s=nullptr;
此语句始终用于 Move 语义 fredoverflow(user) 解释了类似“现在源不再拥有该对象”之类的内容,但我不是在做了。因为如果我没有写这个声明仍然没有问题一切正常。请解释这一点
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;
}
您的 class 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
是“就地”构造,因此不会发生额外的 destruction/move;
否则有一个移动构造函数和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())
使用复制赋值作为移动赋值 returns(正确)A&
4
In Move constructor and Move overloaded = operator I used a.s=nullptr;
This statement is always used in Move semantics fredoverflow(user) explained something like "now the source no longer owns the object it" but I am not getting it. Because if I did not write this statement still no problem everything works fine. please explain this point
你的问题是你复制而不是移动。
如果你s = a.s;
而不是复制
s = new char[strlen(a.s) + 1];
strcpy(s, a.s);
然后 this->s
和 a.s
将指向相同的数据,并且 this
和 a
将在它们的析构函数中释放(相同的)内存 -> double免费错误。
a.s = nullptr;
会解决这个问题。
fredoverflow(用户 237K Rep.)在他的两个答案中解释了大部分内容
但是在实现 Move 构造函数和重载 Move Assignment 运算符时(OMAO
)(我在整个问题中都使用这些简短形式)我遇到了一些问题,我将把它们放在这里。
用户 Greg Hewgill(拥有 826K Rep. 的用户)也有另一个答案
我引用他的话,
Suppose you have a function that returns a substantial object then an ordinary C++ compiler will create a temporary object for the result of multiply(), call the copy constructor to initialize r, and then destruct the temporary return value. Move semantics in C++0x allow the "move constructor" to be called to initialize r by copying its contents, and then discard the temporary value without having to destruct it.
我也会提到这个问题。
好的我开始
代码
.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());
然后移动构造函数调用,那么为什么会这样? - 为什么 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
- 现在这里复制构造函数和析构函数为由于 return *this 来自 Move 重载 = 运算符函数而创建的临时对象运行。根据 Greg Hewgill 的说法,
C++ 0x
允许调用 Move 构造函数来通过复制它的内容来初始化,然后丢弃临时值而不必破坏它。我正在使用C++11
但仍然通过创建临时对象、复制构造函数来完成初始化。 - 我不知道第二个析构函数是哪个对象 运行?
3_main
fredoverflow(用户 237K Rep.)保留了 return 类型的 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'
所以我觉得 return 类型应该是 A&&
或 A
但是 A&&
也会报错 [ERROR] can't bind a lvalue to a&&
所以 return 类型必须是 A
,对吗?
4
在 Move 构造函数和 Move 重载 = 运算符中,我使用了 a.s=nullptr;
此语句始终用于 Move 语义 fredoverflow(user) 解释了类似“现在源不再拥有该对象”之类的内容,但我不是在做了。因为如果我没有写这个声明仍然没有问题一切正常。请解释这一点
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;
}
您的 class 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
是“就地”构造,因此不会发生额外的 destruction/move;
否则有一个移动构造函数和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())
使用复制赋值作为移动赋值 returns(正确)A&
4 In Move constructor and Move overloaded = operator I used
a.s=nullptr;
This statement is always used in Move semantics fredoverflow(user) explained something like "now the source no longer owns the object it" but I am not getting it. Because if I did not write this statement still no problem everything works fine. please explain this point
你的问题是你复制而不是移动。
如果你s = a.s;
而不是复制
s = new char[strlen(a.s) + 1];
strcpy(s, a.s);
然后 this->s
和 a.s
将指向相同的数据,并且 this
和 a
将在它们的析构函数中释放(相同的)内存 -> double免费错误。
a.s = nullptr;
会解决这个问题。