移动构造函数演示
#include <iostream>
using namespace std;
class B
{
public:
B() :m_bm(100) { cout << "执行了类B的构造函数" << endl; }
B(const B& tmp) : m_bm(tmp.m_bm) { cout << "执行了一次类B的拷贝构造函数" << endl; }
virtual ~B() { cout << "执行了类B的析构函数" << endl; };
int m_bm;
};
class A
{
public:
A() : m_pb(new B()) { cout << "执行了类A的构造函数" << endl; }
A(const A& tmpa) : m_pb(new B(*(tmpa.m_pb))) { cout << "执行了类A的拷贝构造函数" << endl; }
~A()
{
delete m_pb;
cout << "类A的析构函数执行了" << endl;
}
private:
B* m_pb;
};
static A getA()
{
A a;
return a;
}
int main(void)
{
//B* pb = new B();
//pb->m_bm = 19;
//B* pb2 = new B(*pb);
//delete pb;
//delete pb2;
A a = getA();
return 0;
}
运行结果
重点说一下类A的拷贝构造是怎么来的,这个实际上是用来产生临时对象的,将类A拷贝给临时对象.
在类A中添加移动构造函数
A(A&& tmpa) { cout << "执行了类A的移动构造函数" << endl; }
运行结果
可以发现,刚刚的拷贝赋值构造函数改成了移动构造函数,说明系统比较只能,有他自己的判断.他发现调用移动构造更合适的时候它会调用移动构造函数,尤其是生成了临时这种情形,更该调用移动构造.因为临时对象是右值.
虽然类A的移动构造函数一级写出来了,但是其中没有实质性的代码,程序员要负责在其中加入有实际意义的工作代码,完成应该完成的功能.
补充
A(A&& tmpa): m_pb(tmpa.m_pb) //原对象由临时对象接管
{
tmpa.m_pb = nullptr; //切断原对象指向的内存
cout << "执行了类A的移动构造函数" << endl;
}
测试
//main
A a = getA();
//A a1(a); //这里还是调用的拷贝构造
A a1(std::move(a)); //当实参是右值,调用移动构造函数
//真正通过移动构造函数节省了效率
移动赋值运算符
#include <iostream>
using namespace std;
class B
{
public:
B() :m_bm(100) { cout << "执行了类B的构造函数" << endl; }
B(const B& tmp) : m_bm(tmp.m_bm) { cout << "执行了类B的拷贝构造函数" << endl; }
virtual ~B() { cout << "执行了类B的析构函数" << endl; };
int m_bm;
};
class A
{
public:
A() : m_pb(new B()) { cout << "执行了类A的构造函数" << endl; }
A(const A& tmpa) : m_pb(new B(*(tmpa.m_pb))) { cout << "执行了类A的拷贝构造函数" << endl; }
A(A&& tmpa)noexcept : m_pb(tmpa.m_pb)
{
tmpa.m_pb = nullptr;
cout << "执行了类A的移动构造函数" << endl;
}
//拷贝赋值运算符
A& operator = (const A& src)
{
if (this == &src)
{
return *this;
}
delete m_pb;
m_pb = new B(*src.m_pb); // 调用拷贝构造函数
cout << "类A的拷贝赋值运算符执行了" << endl;
}
//移动赋值运算符,末尾加noexcept,这里作用就是不抛出异常,优化效率
A& operator = (A&& src) noexcept
{
if (this == &src)
return *this;
delete m_pb;
m_pb = src.m_pb; //对方的内存直接拿过来
src.m_pb = nullptr; //把自己原来的这块内存释放掉
cout << "类A的移动赋值运算符执行了" << endl;
}
~A()
{
delete m_pb;
cout << "执行了类A的析构函数" << endl;
}
private:
B* m_pb;
};
static A getA()
{
A a;
return a;
}
int main(void)
{
//B* pb = new B();
//pb->m_bm = 19;
//B* pb2 = new B(*pb);
//delete pb;
//delete pb2;
//A a = getA();
//A a1(a); //拷贝赋值运算符
//A a1(std::move(a)); //移动构造函数
A a2; //普通构造
a2 = std::move(a); //移动赋值运算符
return 0;
}
合成的移动操作
(1)如果一个类定义了自己的拷贝构造函数,拷贝赋值运算符或者析构函数(这三者之一,表示程序员要自己处理对象的复制或释放问题),编译器就不会为它合成移动构造函数,这样就可以防止编译器合成一个完全不是程序员自己想要的移动构造函数或者移动赋值运算符
(2)如果类中没有提供移动构造函数和移动赋值运算符,那么前面调用类中移动构造函数和移动赋值运算符的代码行会去调用类中的拷贝构造和拷贝赋值运算符来代替,虽然移动移动构造函数与拷贝构造函数的形参并不相同以及移动赋值运算符和拷贝赋值运算符的形参并不相同.
(3)只有一个类没定义任何自己版本的拷贝构造函数,拷贝赋值运算符,析构函数,且类的每个非静态成员都可以移动时,编译器才会为该类合成移动构造函数或者移动赋值运算符.
解释一下成员可以移动:
①内置类型(整型,实型等)的成员变量可以移动.
②如果成员变量是一个类类型,如果这个类有对应的移动操作相关的函数,则该成员变量可以移动.
示例
#include <iostream>
#include <string.h>
using namespace std;
//
//class B
//{
//public:
// B() :m_bm(100) { cout << "执行了类B的构造函数" << endl; }
// B(const B& tmp) : m_bm(tmp.m_bm) { cout << "执行了类B的拷贝构造函数" << endl; }
//
// virtual ~B() { cout << "执行了类B的析构函数" << endl; };
// int m_bm;
//};
//class A
//{
//public:
// A() : m_pb(new B()) { cout << "执行了类A的构造函数" << endl; }
// A(const A& tmpa) : m_pb(new B(*(tmpa.m_pb))) { cout << "执行了类A的拷贝构造函数" << endl; }
// A(A&& tmpa)noexcept : m_pb(tmpa.m_pb)
// {
// tmpa.m_pb = nullptr;
// cout << "执行了类A的移动构造函数" << endl;
// }
//
// //拷贝赋值运算符
// A& operator = (const A& src)
// {
// if (this == &src)
// {
// return *this;
// }
// delete m_pb;
// m_pb = new B(*src.m_pb); // 调用拷贝构造函数
// cout << "类A的拷贝赋值运算符执行了" << endl;
// }
// //移动赋值运算符,末尾加noexcept,这里作用就是不抛出异常,优化效率
// A& operator = (A&& src) noexcept
// {
// if (this == &src)
// return *this;
//
// delete m_pb;
// m_pb = src.m_pb; //对方的内存直接拿过来
// src.m_pb = nullptr; //把自己原来的这块内存释放掉
// cout << "类A的移动赋值运算符执行了" << endl;
// }
//
// ~A()
// {
// delete m_pb;
// cout << "执行了类A的析构函数" << endl;
// }
//private:
// B* m_pb;
//
//};
//
//static A getA()
//{
// A a;
// return a;
//}
//
//int main(void)
//{
// //B* pb = new B();
// //pb->m_bm = 19;
// //B* pb2 = new B(*pb);
// //delete pb;
// //delete pb2;
//
// A a = getA();
// //A a1(a); //拷贝赋值运算符
// //A a1(std::move(a)); //移动构造函数
//
// A a2; //普通构造
// a2 = std::move(a); //移动赋值运算符
//
// return 0;
//}
struct TC
{
int i;
std::string s;
};
int main(int argc, char** argv)
{
TC a;
a.i = 100;
a.s = "Hello World!";
TC b = std::move(a);
return 0;
}
通过断点可以发现,执行完上面代码后,s已经为空("")了,这是执行了string类中移动构造的结果