定义
在默认情况下(用户没有定义,但是也没有显式的删除),编译器会自动的隐式生成一个拷贝构造函数和赋值运算符。但用户可以使用delete
来指定不生成拷贝构造函数和赋值运算符,这样的对象就不能通过值传递,也不能进行赋值运算。
显示定义:
class Preson
{
public:
// 拷贝构造函数
Preson(const Preson& other)
{
}
// 赋值运算符函数
Preson& operator=(const Preson& other)
{
}
}
C++11新特性,显示删除:
class Preson
{
public:
// 拷贝构造函数
Preson(const Preson& other) = delete;
// 赋值运算符函数
Preson& operator=(const Preson& other) = delete;
}
注意:拷贝构造函数的参数一定是以
引用的方式传递的
,如果使用值传递的方式,强出现无限循环递归,栈会溢出。
何时调用
#include <iostream>
using namespace std;
class Preson
{
public:
Preson() {}
Preson(const Preson& other)
{
cout << "拷贝构造函数" << endl;
name = other.name;
}
Preson& operator=(const Preson& other)
{
cout << "赋值运算符函数" << endl;
name = other.name;
return *this;
}
private:
string name;
};
void Fun1(Preson p)
{
}
Preson Fun2()
{
Preson p;
return p;
}
int main()
{
Preson p;
cout << "1" << endl;
Preson p1 = p;
Preson p2;
cout << "2" << endl;
p2 = p;
cout << "3" << endl;
Fun1(p);
cout << "4" << endl;
p2 = Fun2();
cout << "5" << endl;
Preson p3 = Fun2();
}
运行结果如下:
- 虽然出现的
=
,但是实际上使用对象p来创建一个新的对象p1,也就是构造新的对象,所以调用的是拷贝构造函数
。 - 首先声明一个对象p2,然后使用赋值运算符”=”,将p的值复制给p2,显然是调用赋值运算符,为一个已经存在的对象赋值,所以调用的是
赋值运算符函数
。 - 以
值传递的方式
将对象p2传入函数Fun1内,调用拷贝构造函数构建一个函数Fun1可用的实参
,所以调用的是拷贝构造函数
。 - 在函数Fun2以值传递的方式返回时,用拷贝构造函数创建一个临时对象作为返回值,,所以调用的是
拷贝构造函数
。在函数返回后将临时变量赋值给已经初始化过的p2,所以调用的是赋值运算符函数
。 - 如果按照4的情况解释,应该是首先调用拷贝构造函数创建临时对象;然后再调用拷贝构造函数使用刚才创建的临时对象创建新的对象p3,也就是会调用两次拷贝构造函数。可能是编译器优化了,应该是直接调用拷贝构造函数使用返回值创建了对象p3,所以只调用了一次
拷贝构造函数
。
拷贝构造函数
- 当对象作为函数的参数,以值传递的方式传递给函数时。
- 当对象作为函数的返回值,以值传递的方式返回时。
- 当使用一个对象初始化另一个对象时。
赋值运算符函数
当一个对象赋值给另一个已经初始化完成的对象时。
什么时候需要使用
当成员变量里有指针类型的时候,因为默认的拷贝构造函数和赋值运算符函数都是值的复制是浅拷贝的
,对于指针只是简单的值复制并不能分割开两个对象的关联,任何一个对象对该指针的操作都会影响到另一个对象。这时候就需要提供自定义的深拷贝
的拷贝构造函数,消除这种影响。
通常的原则是:
- 含有指针类型的成员或者有动态分配内存的成员都应该提供自定义的拷贝构造函数。
- 在提供拷贝构造函数的同时,还应该考虑实现自定义的赋值运算符。
- 对于值类型的成员进行值复制。
- 对于指针和动态分配的空间,在拷贝中应重新分配分配空间。
- 对于基类,要调用基类合适的拷贝方法,完成基类的拷贝。
总结
- 拷贝构造函数和赋值运算符的行为比较相似,却产生不同的结果;拷贝构造函数
使用已有的对象创建一个新的对象
,赋值运算符是将一个对象的值复制给另一个已存在的对象
。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生
。 - 关于深拷贝和浅拷贝。当类有指针成员或有动态分配空间,都应实现自定义的拷贝构造函数。提供了拷贝构造函数,最后也实现赋值运算符。