1、拷贝构造函数的出现场景
以下是String类的一个简单定义,用于说明一些例子:
class String
{
public:
String(const char *ch=NULL); //默认构造函数
String(const String &str); //拷贝构造函数
~String(void); //析构函数
String& operator=(const String &str); //赋值构造函数
private:
char *m_data;
};
当用一个已初始化的类类型对象初始化一个新的对象时,拷贝构造函数就会被调用,拷贝构造函数会出现在以下三种情景:
1) 一个对象以值传递的方式传入函数体
void InputString(String obj)
{
//函数具体逻辑
}
2) 一个对象以值传递的方式从函数返回
String OutputString()
{
return String("hello world");
}
3) 使用一个对象初始化另一个对象
String a(1);
String b = a; //通过对象a初始化对象b
2、深拷贝和浅拷贝的区别
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
当用户没有自定义拷贝构造函数时,编译器会自动生成一个默认的拷贝构造函数,并以位拷贝(位拷贝就是浅拷贝)的方式完成对象之间的赋值。关于深拷贝和浅拷贝的一个经典例子就是String类的实现,以下代码使用一个String类对象初始化另一个String类对象:
String a("Shallow Copy");
String b = a;
若上面的代码没有重写拷贝构造函数,就会出现以下的问题:
1) 对象a和对象b的成员变量m_data将会指向了同一内存区域,其中一方的改变将会影响另一方;
2) 当对象析构的时候,对象a的成员变量m_data将会被析构两次,造成程序崩溃。
3、拷贝构造函数和赋值构造函数
所谓赋值构造函数,就是操作符"="的重载函数,它的作用与拷贝构造函数类似,若没有重写赋值构造函数,编译器也会自动生成一个默认的以位拷贝工作的赋值构造函数。
String a("hello");
String b("world");
b = a;
若上面的代码没有重写赋值构造函数,则会出现以下问题:
1) 对象b的成员变量m_data原来指向的内存区域没有被释放,造成内存泄露;
2) 对象a和对象b的成员变量m_data将会指向了同一内存区域,其中一方的改变将会影响另一方
3) 当对象析构的时候,对象a的成员变量m_data将会被析构两次,造成程序崩溃。
不是说有等于号的时候就是赋值构造函数,有的时候拷贝构造函数和赋值构造函数容易混淆,看以下例子:
String a("hello");
String b("world");
String d = a; //拷贝构造函数
b = a; //赋值构造函数
当用一个已存在的对象初始化一个未存在的对象时,调用的是拷贝构造函数;当用一个已存在的对象给另一个已存在的对象赋值时,调用的是赋值构造函数,它也仅出现在这种情况。
为了设计出更安全的类,需要使用到拷贝构造函数和赋值构造函数时,则重写一个深拷贝的版本;若不需要,则禁用这两个函数,免得出现一些不应该的错误。只要对拷贝构造函数和赋值构造函数的函数访问级别设置为private即可以禁用这两个函数,以前面的String简单定义代码为例子:
class String
{
public:
String(const char *ch=NULL); //默认构造函数
~String(void); //析构函数
private:
String(const String &str); //拷贝构造函数
String& operator=(const String &str); //赋值构造函数
private:
char *m_data;
};