c++的拷贝有两种:即 深拷贝和 浅拷贝.
1.深拷贝和浅拷贝
- 在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。
- 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
例如:
c++的默认构造函数就是浅拷贝,浅拷贝就是简单的赋值操作,如果对象中没有其他的资源(如:堆,文件,系统资源等),则深拷贝和浅拷贝没有什么区别,如:
class A
{
public:
A(int _data) : data(_data){}
private:
int data;
};
int main()
{
A a(5), b = a; // 仅仅是数据成员之间的赋值
}
这是浅拷贝,b=a调用了默认类中的拷贝构造函数,在这里使用深浅拷贝并没有任何区别。但是下面这种情况就不一定了:
class A
{
public:
A(int _size) : size(_size)
{
data = new int[size];
} // 假如其中有一段动态分配的内存
~A()
{
delete [] data;
} // 析构时释放资源
private:
int* data;
int size;
}
int main()
{
A a(5), b = a;
}
这时,这里构造函数中有堆内存的分配,b=a也是调用的默认的拷贝构造函数,这时会出现一个什么样的情况呢?
a和b指向了同一块内存空间,所以当调用析构函数的时候,内存空间会被析构两次,所以这将导致内存泄露或程序崩溃。
怎么解决呢?深拷贝–自定义拷贝构造函数
class A
{
public:
A(int _size) : size(_size)
{
data = new int[size];
} // 假如其中有一段动态分配的内存
A(){};
A(const A& _A) : size(_A.size) //拷贝构造
{
data = new int[size];
} // 深拷贝
~A()
{
delete [] data;
} // 析构时释放资源
private:
int* data;
int size;
}
int main()
{
A a(5), b = a;
}
这时我们的内存是什么样的情况呢,
a和b都被开辟一个内存空间,这样就没问题了,但是这样又引出一个问题。假如对象a被赋值之后立即消亡,我们还需要多出一次分配内存空间的资源消耗,这不是浪费吗?
所以对于这个问题,c++11当中提出一个移动构造函数(std::move)来解决这个问题。
2. 移动构造函数(std::move())
移动构造内存是这样的:
就是让这个临时对象它原本控制的内存的空间转移给构造出来的对象,这样就相当于把它移动过去了。
复制构造和移动构造的差别:
这种情况下,我们觉得这个临时对象完成了复制构造后,就不需要它了,我们就没有必要去首先产生一个副本,然后析构这个临时对象,这样费两遍事,又占用内存空间,干脆将临时对象原本的堆内存直接转给构造的对象就行了。 当临时对象在被复制后,就不再被利用了。我们完全可以把临时对象的资源直接移动,这样就避免了多余的复制构造。
什么时候该触发移动构造呢?
如果临时对象即将消亡,并且它里面的资源是需要被再利用的,这个时候我们就可以触发移动构造。
std::move函数可以以非常简单的方式将左值转换为右值引用。
通过std::move,可以避免不必要的拷贝操作。
std::move是为性能而生。
int main()
{
string str = "Hello";//这里假设我们只需要将str的内容放到vector中,完成以后永远都不需要再用到str
vector<string> v;
//调用常规的拷贝构造函数,新建字符数组,拷贝数据
v.push_back(str);
cout << "After copy, str is :" << str << endl;
//先把str转为右值引用,然后调用移动构造函数转交所有权
v.push_back(move(str));
cout << "After move, str is:" << str << endl;
cout << "The contents of the vector are:{" << v[0]
<< "," << v[1] << "}"<<endl;
return 0;
}
调用move之后,用完就会消亡。
3.左值和右值
什么是左值,什么是右值,简单说左值可以赋值,右值不可以赋值。以下面代码为例,“ A a = getA();”该语句中a是左值,getA()的返回值是右值。
具体的使用可以看这篇blog: 左值右值