拷贝构造函数和赋值运算符
默认拷贝构造函数和赋值运算符函数
- 在默认情况下(用户没有定义,但是也没有显式的删除),编译器会自动的隐式生成一个拷贝构造函数和赋值运算符函数(缺省的)。
禁止拷贝和赋值
- 使用delete来指定不生成拷贝构造函数和赋值运算符,这样的对象就不能通过值传递,也不能进行赋值运算。
class Person
{
public:
...
Person(const Person& p) = delete;
Person& operator=(const Person& p) = delete;
private:
int age;
string name;
};
- 如果我们不想实现拷贝构造函数和赋值运算符函数,又不允许别人使用编译器隐式生成的缺省函数,同时也不想显式地删除拷贝构造函数和赋值运算符函数,我们还可以通过将拷贝构造函数和赋值运算符函数声明成类私有函数的方式来实现。如下所示:
class Person
{
public:
...
private:
Person(const Person& p); //以常量引用的方式传递参数
Person& operator=(const Person& p);//返回值类型为该类型的引用
int age;
string name;
};
声明
- 拷贝构造函数必须以引用的方式传递参数,基本上都是传常量引用的方式传递函数参数。这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。
- 赋值运算符函数的返回值类型要声明为该类型的引用,并在函数结束前返回实例自身的的引用(*this),只有返回一个引用,才能进行连续赋值。否则,如果函数的返回值是void,则应用改赋值运算符将不能进行连续赋值。假设有3个Person对象:p1、p2、p3,在程序中语句p1=p2=p3将不能通过编译。
何时调用
- 拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符函数是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。调用的是拷贝构造函数还是赋值运算符函数,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符函数。
- 调用拷贝构造函数的场景
- 对象作为函数的参数,以值传递的方式传给函数。
- 对象作为函数的返回值,以值的方式从函数返回。在返回时会调用拷贝构造函数创建一个临时对象tmp作为返回值。
- 使用一个对象给另一个对象初始化。
// 运行环境为 vs2019
#include <iostream>
using namespace std;
class Person
{
public:
Person() {}
Person(const Person& p)
{
cout << " Copy Constructor" << endl;
}
Person& operator=(const Person& p)
{
cout << " Assign" << endl;
return *this;
}
private:
int age;
string name;
};
void f(Person p)
{
return;
}
Person f1()
{
Person p;
return p;
}
int main()
{
Person p;
cout << "Person p1 = p;" << endl;
Person p1 = p; // 1.拷贝构造函数
Person p2;
cout << "p2 = p;" << endl;
p2 = p; // 2.赋值运算符
cout << "f(p2);" << endl;
f(p2); // 3.拷贝构造函数
cout << "p2 = f1();" << endl;
p2 = f1(); // 4.拷贝构造函数 + 赋值运算符
cout << "Person p3 = f1();" << endl;
Person p3 = f1(); // 5.拷贝构造函数
return 0;
}
-
运行结果
-
场景分析
- 这是虽然使用了”=”,但是实际上使用对象p来创建一个新的对象p1。也就是产生了新的对象,所以调用的是拷贝构造函数。
- 首先声明一个对象p2,然后使用赋值运算符”=”,将p的值复制给p2,显然是调用赋值运算符函数,为一个已经存在的对象赋值 。
- 以值传递的方式将对象p2传入函数f内,调用拷贝构造函数构建一个函数f可用的实参。
- 这条语句拷贝构造函数和赋值运算符函数都调用了。函数f1以值的方式返回一个Person对象,在返回时会调用拷贝构造函数创建一个临时对象tmp作为返回值;返回后调用赋值运算符函数将临时对象tmp赋值给p2.
- 按照4的解释,应该是首先调用拷贝构造函数创建临时对象;然后再调用拷贝构造函数使用刚才创建的临时对象创建新的对象p3,也就是会调用两次拷贝构造函数。不过,编译器也没有那么傻,应该是直接调用拷贝构造函数使用返回值创建了对象p3。
深拷贝与浅拷贝
简述
- 浅拷贝:只是对指针的拷贝,拷贝后两个指针指向同一个内存空间。
- 深拷贝:不仅对指针进行拷贝,而且对指针指向的内容进行拷贝,经拷贝后两指针分别指向不同的内存地址。
- 简单理解:假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。
拷贝构造函数和赋值运算符的实现
- 对于值类型的成员进行值复制
- 对于指针和动态分配的空间,在拷贝中应重新分配分配空间
- 对于基类,要调用基类合适的拷贝方法,完成基类的拷贝
参考
https://blog.youkuaiyun.com/huangjw_806/article/details/79134330
May 28, 2020 PM 15:12 @DaYang