一:对象移动的概念
对象拷贝,c++11 “对象移动”
二:移动构造函数和移动赋值运算符概念
C++11 std::move 效率问题:强制把一个左值转成一个右值
移动构造函数:C++11 进一步提高程序效率
说明:
<1>A移动B,那么A对象我们就不能再使用了。
<2>移动:并不是把内存中的数据从一个地址倒腾到另一个地址,只是所有者变更。
拷贝构造函数:Time::Time(const Time& tmptime, int a = 30){…} const 左值引用
移动构造函数:Time::Time(const Time &&tmptime) 右值引用 && 。
拷贝构造函数和移动构造函数若有其他参数,则其他参数必须要有默认值。
移动构造函数和移动赋值运算符应该完成的功能:
<1>完成必要的内存移动,斩断原对象和内存的关系。
<2>确保移动后原对象处于一种"即便被销毁也没有什么问题"的一种状态,确保不再使用原对象。
三:移动构造函数演示
class B
{
public:
B() :m_bm(100)
{
cout << "类B的构造函数执行了" << endl;
}
B(const B& temp)
{
m_bm = temp.m_bm;
cout << "类B的拷贝构造函数执行了" << endl;
}
virtual ~B()
{
cout << "类B的析构函数执行了" << endl;
}
private:
int m_bm;
};
class A
{
public:
A() :m_pb(new B())
{
cout << "类A的构造函数执行了" << endl;
}
A(const A& tempa) : m_pb(new B(*tempa.m_pb))
{
cout << "类A的拷贝构造函数执行了" << endl;
}
~A()
{
delete m_pb;
cout << "类A的析构函数执行了" << endl;
}
private:
B* m_pb;
};
static A getA()
{
A a;
cout << "******************" << endl;
return a; //这里会执行类A的移动构造函数,把对象a的数据移动给要返回的临时对象
}
int main()
{
A a = getA();
return 0;
}
执行结果:
类B的构造函数执行了
类A的构造函数执行了
******************
类B的拷贝构造函数执行了
类A的拷贝构造函数执行了
类B的析构函数执行了
类A的析构函数执行了
类B的析构函数执行了
类A的析构函数执行了
向类A中添加移动构造函数,如下:
A(const A&& tempa)
{
cout << "类A的移动构造函数执行了" << endl;
}
执行结果:
类B的构造函数执行了
类A的构造函数执行了
******************
类A的移动构造函数执行了
类B的析构函数执行了
类A的析构函数执行了
类A的析构函数执行了
改造一下类A的移动构造函数,加入有实际意义的工作代码,如下:
A(A&& tempa): m_pb(tempa.m_pb)
{
tempa.m_pb = nullptr; //打断原对象中m_pb所指向的内存
cout << "类A的移动构造函数执行了" << endl;
}
int main()
{
A a = getA();
A a1(a);
return 0;
}
执行结果:
类B的构造函数执行了
类A的构造函数执行了
******************
类A的移动构造函数执行了
类A的析构函数执行了
类B的拷贝构造函数执行了
类A的拷贝构造函数执行了
类B的析构函数执行了
类A的析构函数执行了
类B的析构函数执行了
类A的析构函数执行了
noexcept关键字:
用来通知编译器该移动构造函数不抛出任何异常(提高编译器工作效率,否则编译器会为可能抛出异常的函数做一些额外的处理准备工作)。
如果加了noexcept关键字,但是该程序里抛出了异常,那么整个程序会被终止运行。
如果移动构造函数的函数声明和函数实现分开的话,声明和实现部分都要家noexcept关键字。
改造类A的移动构造函数,添加noexcept关键字,如下:
A(A&& tempa) noexcept : m_pb(tempa.m_pb)
{
tempa.m_pb = nullptr; //打断原对象中m_pb所指向的内存
cout << "类A的移动构造函数执行了" << endl;
}
int main()
{
A a = getA();
A a1(std::move(a));
//A&& a1(std::move(a)); //不产生新对象,不会调用类A的移动构造函数
//效果等同于把对象a的名字修改为a2;a和a2代表用一个对象
return 0;
}
执行结果:
类B的构造函数执行了
类A的构造函数执行了
******************
类A的移动构造函数执行了
类A的析构函数执行了
类A的移动构造函数执行了
类B的析构函数执行了
类A的析构函数执行了
类A的析构函数执行了
int main()
{
A&& a = getA();
return 0;
}
执行结果:
类B的构造函数执行了
类A的构造函数执行了
******************
类A的移动构造函数执行了
类A的析构函数执行了
类B的析构函数执行了
类A的析构函数执行了
四:移动赋值运算符
class B
{
public:
B() :m_bm(100)
{
cout << "类B的构造函数执行了" << endl;
}
B(const B& temp)
{
m_bm = temp.m_bm;
cout << "类B的拷贝构造函数执行了" << endl;
}
virtual ~B()
{
cout << "类B的析构函数执行了" << endl;
}
private:
int m_bm;
};
class A
{
public:
A() :m_pb(new B())
{
cout << "类A的构造函数执行了" << endl;
}
A(const A& tempa) : m_pb(new B(*tempa.m_pb))
{
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;
return *this;
}
A(A&& tempa) noexcept : m_pb(tempa.m_pb)
{
tempa.m_pb = nullptr; //打断原对象中m_pb所指向的内存
cout << "类A的移动构造函数执行了" << endl;
}
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;
return *this;
}
~A()
{
delete m_pb;
cout << "类A的析构函数执行了" << endl;
}
private:
B* m_pb;
};
static A getA()
{
A a;
cout << "******************" << endl;
return a;
}
int main()
{
A a = getA(); //移动构造,临时对象直接构造在a上
A a2; //普通构造
//a2 = a; //拷贝赋值运算符
a2 = std::move(a); //移动赋值运算符
}
五:合成的移动操作
某些条件下,编译器能够合成移动构造函数,移动赋值运算符
<1>有一个类有自己的拷贝构造函数,拷贝赋值运算符或者自己的析构函数,这几者之一,那么编译器就不会为它合成移动构造函数和移动赋值运算符;
所以 有一些类是没有移动构造函数和移动赋值运算符的。
<2>如果我们没有自己的移动构造函数和移动赋值运算符,那么系统会调用我们自己写的拷贝构造函数和拷贝赋值运算符来代替;
<3>只有一个类没有定义自己版本的拷贝构造成员(没有拷贝构造函数也没有拷贝赋值运算符),析构函数,且类的每个非静态成员都可以移动时,编译器才会为该类合成移动构造函数或者移动赋值运算符。
什么叫成员可以移动呢?
<1>内置类型是可以移动的
<2>类类型的成员,则这个类要有对应的移动操作相关的函数,就可以移动。此时编译器就能够为而我们
struct TC
{
int i;
string s;
~TC() //如果增加一个析构函数,a.s不再为空,系统不会为TC合成移动构造函数
{
}
};
int main()
{
TC a;
a.i = 100;
a.s = "hello";
const char* p = a.s.c_str();
TC b = std::move(a); //导致TC类的移动构造函数(系统帮助我们生成的)的执行。string类里的移动构造函数把a.s移动到b.s,
//而不是std::move移动的
const char* q = b.s.c_str();
}
ps: p 和 q 指向的内存地址也是不同的,这应该是string类的特性所导致,这里虽然执行string的移动构造函数,但是似乎没有节省什么性能。
六:总结
<1>尽量给类增加移动构造函数和移动赋值运算符
<2>noexcept
<3>该给nullptr的就要给nullptr,原对象要置空,让被移动对象随时处于一种能够被析构的状态
<4>没有移动,会调用拷贝