对象移动
C++11引入对象移动;进行所有权的转移;
移动构造函数和移动赋值运算符应该完成的功能
- 确保必要的内存移动,斩断原对象和内存的关系;
- 确保移动后原对象处于一种“即便被销毁也没有什么问题”的一种状态;A ---> B,确保不再使用A,而是应该去使用B;
移动构造函数
引入目的:提高程序效率;
说明:
-
A --> B,那么A对象就不能再使用了;
-
移动:并不是把内存中的数据从一个地址移动到另一个地址,只是内存所有者变更;
拷贝构造函数
class Temp;
Temp::Temp(const Temp& tmp); //参数是const左值引用
移动构造函数
class Temp;
Temp::Temp(Temp&& tmp); //参数是右值引用
#include <iostream>
using namespace std;
class B
{
public:
//默认构造函数
B() :m_b(100)
{
//cout << "类B的构造函数执行了" << endl;
};
//拷贝构造函数
B(const B& tmp) :m_b(tmp.m_b)
{
//cout << "类B的拷贝构造函数执行了" << endl;
};
virtual ~B()
{
//cout << "类B的析构函数执行了" << endl;
}
public:
int m_b;
};
class A
{
public:
A() :m_p_b(new B()) //调用类B的构造函数
{
cout << "类A的构造函数执行了" << endl;
}
A(const A& tmp) :m_p_b(new B(*(tmp.m_p_b))) //调用类B的拷贝构造函数
{
cout << "类A的拷贝构造函数执行了" << endl;
}
//C++11 引入 noexcept 通知标准库,移动构造函数不抛出任何异常,提高编译器工作效率;
//移动构造函数要添加noexcept
A(A&& tmp) noexcept :m_p_b(tmp.m_p_b) //原来对象a指向的内存m_p_b,直接就让这个临时对象指向这段内存;
{
cout << "类A的移动构造函数执行了" << endl;
tmp.m_p_b = nullptr;
}
virtual ~A()
{
delete m_p_b;
cout << "类A的析构函数执行了" << endl;
}
public:
B* m_p_b;
};
static A get_A()
{
A a;
return a; //生成临时对象 调用拷贝构造函数
}
int main()
{
//B* p_b = new B(); //调用类B的构造函数;
//p_b->m_b = 19;
//B* p_b_1 = new B(*p_b); //调用类B的拷贝构造函数;
//delete p_b; //析构函数
//delete p_b_1; //析构函数
A a = get_A(); //调用1次构造,1次拷贝构造,2次析构,程序结束
//A中添加移动构造函数后,就调用移动构造函数
//移动构造函数:把a对象移动给临时对象
//A a = get_A(); 增加移动构造函数后,调用1次构造,1次移动构造,2次析构,程序结束
A a_1(a); //1次拷贝构造函数
//如何让A a_1(a) 调用移动构造函数
//使用str::move
A a_2(std::move(a));
A&& a_2(std::move(a)); //不会产生新对象,不会调用移动构造函数;等同于把对象a有了一个新别名a_2; 后续建议使用a_2操作,不要再使用a;
A&& aa = get_A(); //从getA返回临时对象被a接管了;//调用1次构造,1次移动构造,2次析构,程序结束
return 0;
}
移动赋值运算符
#include <iostream>
using namespace std;
class B
{
public:
//默认构造函数
B() :m_b(100)
{
//cout << "类B的构造函数执行了" << endl;
};
//拷贝构造函数
B(const B& tmp) :m_b(tmp.m_b)
{
//cout << "类B的拷贝构造函数执行了" << endl;
};
virtual ~B()
{
//cout << "类B的析构函数执行了" << endl;
}
public:
int m_b;
};
class A
{
public:
A() :m_p_b(new B()) //调用类B的构造函数
{
cout << "类A的构造函数执行了" << endl;
}
A(const A& tmp) :m_p_b(new B(*(tmp.m_p_b))) //调用类B的拷贝构造函数
{
cout << "类A的拷贝构造函数执行了" << endl;
}
//C++11 引入 noexcept 通知标准库,移动构造函数不抛出任何异常,提高编译器工作效率;
//移动构造函数要添加noexcept
A(A&& tmp) noexcept :m_p_b(tmp.m_p_b) //原来对象a指向的内存m_p_b,直接就让这个临时对象指向这段内存;
{
cout << "类A的移动构造函数执行了" << endl;
tmp.m_p_b = nullptr;
}
virtual ~A()
{
delete m_p_b;
cout << "类A的析构函数执行了" << endl;
}
//拷贝赋值运算符
A& operator=(const A& src)
{
if (this == &src)
return *this;
delete m_p_b; //释放自己的这块内存
m_p_b = new B(*(src.m_p_b)); //重新分配一块内存
cout << "类A的拷贝赋值运算符执行了" << endl;
}
//移动赋值运算符
A& operator=(A&& src) noexcept
{
if (this == &src)
return *this;
delete m_p_b; //释放自己的这块内存
m_p_b = src.m_p_b; //指向对方指向的内存
src.m_p_b = nullptr; //对方置空
cout << "类A的移动赋值运算符执行了" << endl;
}
public:
B* m_p_b;
};
static A get_A()
{
A a;
return a; //生成临时对象 调用拷贝构造函数
}
int main()
{
A a = get_A(); //1个构造函数,1个移动构造函数,1个析构函数
A a_1; //1个构造函数
a_1 = std::move(a); //调用移动赋值运算符
return 0;
}
合成的移动操作
某些条件下,编译器会合成移动构造函数,移动赋值运算符
- 有自己的拷贝构造函数,自己的拷贝赋值运算符,或者自己的析构,那么编译器就不会为它合成移动构造函数和移动赋值运算符
- 如果没有自己的移动构造函数和移动赋值运算符,系统会调用我们自己写的拷贝构造函数和拷贝赋值运算符来代替;
- 只有一个类没有定义自己的拷贝构造成员(拷贝构造函数和拷贝运算符),且类中的每个非静态成员都可以移动时,编译器才会为该类合成移动构造函数或者移动赋值运算符;
什么叫做成员可以移动?
-
内置类型可以移动;
-
类类型成员,如果这个类有对应的移动操作相关的函数,就可以移动;
//举例 struct TC { int i; //内置类型可以移动 string a; //string类中定义了自己的移动成员 } int main() { TC a; a.i = 100; a.s = "Hello world"; const char *p = a.s.c_str(); TC b = std::move(a); //导致TC类的合成移动构造函数(编译器生成的)执行; const char *q = b.s.c_str(); //p和q的地址不一样,由于string对象的特性决定的; //这种移动,不是真正的移动,只是拷贝; return 0; }
总结
- 尽量给类增加移动构造函数和移动赋值运算符;
- noexcept;
- 记得将被移动对象赋值给nullptr,让被移动对象随时处于一种能够被析构的状态;
- 没有移动,会调用拷贝代替;
原文链接: https://www.cnblogs.com/NaughtyCoder/p/13359469.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;
也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/369594
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!