对于一个持有指针元素,指针指向堆上资源的值语义类,需要手工增加拷贝赋值和拷贝构造函数。这样发生拷贝操作时,源对象和目的对象持有不同的资源,可以分别析构,这就是值语义。
class A{
public:
explicit A(int i, string s):_i(i)
{
_s= new string(s);
cout<<"A construct fun"<<endl;
}
A(const A &a)
{
_s = new string(a._s);
_i = a._i;
cout<<"A copy construct fun"<<endl;
}
~A()
{
delete _s;
}
A& operator=(const A&a)
{
if(this == &a)
return *this;
i = a._i;
*_s = *(a._s);
}
int value()
{
return _i;
}
string * getStr()
{
return _s;
}
private:
int _i;
string * _s;
}
考虑下面这种情况,
//定义一个数组容器
vector<A> va;
A a1(1,"this a 1");
//va中加入变量,va在堆上分配一个sizeof(va)空间,A:A(const &a)初始化
va.push_back(a1); //这时va.capacity()为1,va已经满了
A a2(2,"this a 2");
cout<<"--------------------------------"<<endl;
//va已经满了 继续添加需要重新分配空间,并把原空间的资源拷贝到新空间,释放原空间资源
va.push_pack(a2); //这一步发生了两次拷贝构造,一次析构
cout<<"--------------------------------------"<<endl;
实际上,第二次添加时,重新分配空间时,我们需要的是把原空间的资源转移到新空间。但是,C++却执行了深拷贝。就像有个笑话所说,如何把冰箱中的一只大象放到另一个冰箱中,c++11之前是这样做的,1,启动量子复制,拷贝一只新大象在目标冰箱内。2,使用激光武器,把源冰箱中的大象毁尸灭迹。c++11提供了转移语义,目标对象可以窃取源对象持有的资源(这个例子中是*_s), 而且和浅拷贝不同的是,由于移后源失去了资源,成为了空壳,所以移后源是析构安全的。移后源依然能够赋值,只是无法读取资源。C++11增加了转移语义,但是移动构造和移动赋值需要手工写。参数使用&&符号和拷贝构造,拷贝赋值区分开来。
// 必须加noexcept,c++ 认为不同修饰符函数是不同的函数(重载)
A(A &&a) noexcept
{
cout<<"move construct fun"<<endl;
_i = a._i;
_s = (a._s);
a._s = NULL;
}
//move copy
A & operator=(A &&a) noexcept
{
cout<<"move copy fun"<<endl;
if(this != &a)
{
_i = a._i;
delete _s;
_s = a._s;
a._s = NULL;
}
}
手工定义了移动函数后,对于转移感知的容器比如vector,在需要移动语义时就会优先调用移动函数,只有没有移动函数时,才会使用copy函数。
完整代码如下:
#include<iostream>
#include <string>
#include <vector>
using namespace std;
class A
{
public:
explicit A(int i,string s):_i(i)
{
_s = new string(s);
cout<<"A construct fun"<<endl;
}
~A()
{
if(_s)
delete _s;
cout<<"A disconstruct fun"<<_i<<endl;
}
A(const A &a)
{
cout<<"A copy construct fun"<<endl;
_i = a._i;
}
A(A &&a) noexcept
{
cout<<"move construct fun"<<endl;
_i = a._i;
_s = (a._s);
a._s = NULL;
}
A & operator=(const A &a)
{
cout<<"copy fun"<<endl;
if(this != &a)
{
_i = a._i;
if (_s==NULL)
_s = new string;
*_s = *(a._s);
cout<<"A operator=()"<<endl;
return *this;
}
}
//move copy
A & operator=(A &&a) noexcept
{
cout<<"move copy fun"<<endl;
if(this != &a)
{
_i = a._i;
delete _s;
_s = a._s;
a._s = NULL;
}
}
int value()
{
return _i;
}
string * getStr()
{
return _s;
}
private:
int _i;
string *_s;
};
A fun()
{
cout<<"entry fun"<<endl;
A a(4,"hello,a");
cout<<"fun,a pointer"<<&a<<endl;
return a;
}
int main()
{
/*
//返回值优化,gcc 编译器默认开启了返回值优化,这个和移动语义不是一个东西,比移动语义效率更高。
A a = fun();
//A a(5);
cout<<"a.value()"<<a.value()<<endl;
cout<<"main, a pointer"<<&a<<endl;
//a失去了资源
A ra(std::move(a));
A b(6,"hello b");
cout<<"b.value() "<<b.value()<<endl;
//空壳依然可以被赋值
a = b;
cout<<"a.value() "<<a.value()<<endl;
cout<<"main a pointer "<<&a<<endl;
*/
A a(0,"this a 0");
vector<A>::size_type n = 1;
cout<<"vector va"<<endl;
vector<A> va;
va.push_back(a);
cout<<"----------------------"<<endl;
//定义了转移函数后,重新分配空间,旧资源被转移到了新空间,少了一次拷贝函数。
va.push_back(a);
cout<<"-----------------------"<<endl;
//va.push_back(std::move(A(1,"hello a")));
//va.push_back(std::move(A(2,"hello a")));
//va.push_back(std::move(A(3,"hello a")));
cout<<"va.capacity(): "<<va.capacity()<<endl;
return 0;
}
g++ -std=c++11 move.cpp -o move
./move