对象移动、移动构造函数、移动赋值运算符[转]
参考:《对象移动-移动构造函数-移动赋值运算符 》 移动构造函数和移动赋值运算符概念 移动构造函数:C++11 进一步提高程序效率。 说明:
(1) A移动B,那么A对象我们就不能再使用A了。 (2) 移动:并不是把内存中的数据从一个地址倒腾到另外一个地址,只是所有者变更。
拷贝构造函数: Time::Time(const Time &tmp, int a = 30) { ... } //const左值引用 移动构造函数: Time::Time(const Time &&tmp, int a = 30) { ... } //右值引用 && 移动构造函数 和移动赋值运算符应该完成的功能
(1) 完成必要的内存移动,斩断原对象和内存的关系。 (2) 确保移动后原对象处于一种“即便被销毁也没有什么问题” 的一种状态。 B <- A ,确保不再使用A, 而是应该去使用B. 示例:
class B{
public:
//默认构造函数
B() :m_bm(100){
cout << "类B的构造函数执行了" << endl;
}
//拷贝构造函数
B(const B& tmp) :m_bm(tmp.m_bm){
//m_bm = tmp.m_bm;
cout << "类B的拷贝构造函数执行了" << endl;
}
virtual ~B(){
cout << "类B的析构函数执行了" << endl;
}
int m_bm;
};
class A{
public:
//默认构造函数
A() : m_pb(new B()) //这里会调用类B的构造函数
{
cout << "类A的构造函数执行了" << endl;
}
//拷贝构造函数
A(const A& tmp) :m_pb(new B(*(tmp.m_pb))){ //调用类B的拷贝构造函数
cout << "类A的拷贝构造函数执行了" << endl;
}
//拷贝赋值运算符
A& operator=(const A& src){
if (this == &src){
return *this;
}
delete m_pb; //把自己这块内存干掉
m_pb = new B(*(src.m_pb)); //重新分配一块内存
std::cout << "类A拷贝赋值运算符执行了" << endl;
}
virtual ~A(){
delete m_pb;
cout << "类A的析构函数执行了" << endl;
}
private:
B *m_pb;
};
B *pb = new B(); // new 调用类B的构造函数
pb->m_bm = 19;
B *pb2 = new B(*pb); //这种给参数的new方法会调用B类的拷贝构造函数
delete pb;
delete pb2;
上面代码:调用了1次构造函数, 1次拷贝构造函数, 2次析构函数从而整个程序执行完毕
添加移动构造函数
//移动构造函数
//noexcept : 通知标准库我们这个移动构造函数不抛出任何异常(提供编译器工作效率)
//A(A &&tmpa) noexcept : m_pb(tmpa.m_pb){
A(A &&tmpa): m_pb(tmpa.m_pb){ //原来对象a 指向的内存m_pb, 我直接就让这个临时对象指向这段内存。
tmpa.m_pb = nullptr;
cout << "类A的移动构造函数执行了" << endl;
}
static A getA(){
A a;
return a; //临时对象, 调用拷贝构造函数。
// 如果类A有移动构造函数,那么会调用移动构造函数: 把a对象的数据移动给了临时对象
}
增加移动构造函数后:执行代码getA(),调用1次构造函数,1次移动构造函数,2次析构函数,从而整个程序执行完毕。 示例2
A a = getA(); //1个构造函数, 1个移动构造函数, 1个析构函数
A a1(a); // 1个拷贝构造函数
A a2(std::move(a)); //std::move() ,建立了新对象,调用新对象a2的移动构造函数
A && a7(std::move(a)); //这里没有建立新对象,根本不会调用什么移动构造函数
//效果等同于把对象a有了一个新别名叫 a2,后续建议用a2来操作,不要再使用名字a
A &&C = getA(); //从getA()返回的临时对象被c接管了。1个析构函数,1个移动构造函数,1个析构函数
移动赋值运算符
//移动赋值运算符
A &operator=(A && src) { //noexcept
if (this == &src)
return *this;
delete m_pb; //把自己的内存先干掉
m_pb = src.m_pb;// 对方的内存直接拿来过(直接指过来)
src.m_pb = nullptr; //斩断源(也就是对方和那个内存的关联我要斩断)
std::cout << "类A移动赋值运算符执行了" << endl;
return *this;
}
A a3 = getA(); //1个构造函数, 1个移动构造函数,1个析构函数
A a4; // 1个构造函数
a4 = std::move(a3); //调用移动赋值运算符。
合成的移动操作
某些条件下,编译器能合成移动构造函数,移动赋值运算符
a) 有自己的拷贝构造函数,自己的拷贝赋值运算符,或者自己的析构,那么编译器就不会为它合成 移动构造函数和移动赋值运算符。 所有 有一些类 是没有移动构造函数和移动赋值运算符的; b) 如果我们没有自己的移动构造函数和移动赋值运算符,那么系统会调用我们自己写的拷贝构造函数和拷贝赋值运算符来代替。 c) 只有一个类没定义任何自己版本的拷贝构造成员,且类的每个非静态成员都可以移动时,编译器才会为该类合成移动构造函数或者移动赋值运算符。 什么叫成员可以移动呢?
(1) 内置类型是可以移动的 (2) 类类型的成员,则这个类要有对应的移动操作相关的函数 ,就可以移动。 此时编译器就能够为我们合成移动构造函数和移动赋值运算符; 示例:
struct TC{
int i; //内置类型可以移动
std::string s; //string类型定义了自己的移动操作
};
总结
(1) 尽量给类增加移动构造函数和移动赋值运算符; (2) 移动构造,移动赋值函数添加 noexcept (3) 该给nullptr的就要给nullptr, 让被移动对象随时处于一种能够被析构的状态; (4) 没有移动,会调用拷贝代替。