您的位置:首页 > 编程语言 > C语言/C++

通过实例分析C++的拷贝构造函数

2014-07-11 20:05 211 查看
源代码heap_data_member.cpp:http://blog.csdn.net/xie_star/article/details/37700187

对象有时是被自动复制的。自动复制发生在对对象作出以下操作时

(1)通过值传递给函数

(2)从函数返回

(3)通过初始式初始化为另一个对象

(4)作为唯一实参传递给对象的构造函数

对象的复制是通过一个名为拷贝构造函数的成员函数来完成的。与构造函数和析构函数一样,如果不编写自己的拷贝构造函数,则编译器为程序员提供一个默认拷贝构造函数。默认拷贝构造函数只是简单的将每个数据成员的值复制给新对象中的同名数据成员,即按成员逐项进行复制。

对于简单的类,默认拷贝构造函数通常没有问题。然而,当类中含有指向堆中值的数据成员时,则应当考虑编写自己的拷贝构造函数。
为什么?

想象一个Critter对象,它有一个指针数据成员指向堆中的string对象。如果只用默认拷贝构造函数,对象的自动复制将会导致新的对象指向堆中的同一个字符串,因为新对象的指针仅仅获得存储在原始对象的指针中地址的一份副本。这种按成员逐项进行的复制造成了浅拷贝,即副本对象的指针数据成员与原始对象的指针数据成员指向同一内存块。

例如,如果没有在Heap Data Member程序中编写自己的拷贝构造函数,那么当通过值传递一个Critter对象来调用下面的函数时,程序将自动生成一个crit的浅拷贝,名为aCopy,存在于testCopyConstructor()中。

testCopyConstructor(crit);

aCopy的m_pName数据成员将与crit的m_pName数据成员指向堆中的同一个string对象。
浅拷贝有什么问题?

一旦testCopyConstructor()函数终止,则aCopy的析构函数会被调用,释放掉其m_pName

数据成员指向的堆中的内存。因此,crit的m_pName数据成员将指向已经被释放掉的内存,

即p_Name将成为一个野指针!

真正需要的是拷贝构造函数能让新生成的对象在堆中拥有自己的内存块,对象中的每个数据成员都指向一个堆中的对象,这就是深拷贝。例如Critter类定义的拷贝构造函数:

Critter(const Critter& c);      //copy constructor prototype

Critter::Critter(const Critter& c)

{

    cout << "Copy constructor called." << endl;

    m_pName = new string(*(c.m_pName));

    m_Age = c.m_Age;

}

正如该拷贝构造函数所示,我们必须使用与类名相同的名称。拷贝构造函数不返回值,但接受一个对类的对象的引用,该对象就是需要复制的对象。引用最好声明为常量引用,以保护原始对象在复制时不被修改。

拷贝构造函数的作用是将原始对象中的任何数据成员复制到副本对象中。
如果原始对象的数据成员时指向堆中值的指针,则拷贝构造函数应当向内存堆请求分配内存,然后将原始的堆中的值复制到新的内存块中,最后让恰当的副本对象的数据成员指向新内存。

当通过值传递crit调用testCopyConstructor()时,编写的拷贝构造函数被自动调用。

这一点可以从显示在屏幕上的文本"Copy constructor called."中判断出来。

拷贝构造函数创建了一个新的Critter对象(副本),并接受原始对象c的引用。通过m_pName = new string(*(c.m_pName));
拷贝构造函数在堆中分配了一块新的内存,然后获取原始对象指向的字符串的副本,再将其复制到新内存中。接下来m_Age = c.m_Age;

只是将原始对象的m_Age的值复制到副本对象的m_Age数据成员之中。

这样对crit完成了一次深拷贝,并且得到了在testCopyConstructor()中使用的aCopy。

当调用aCopy的Greet()成员函数时,可以看到拷贝构造函数起了作用。在运行时,成员函数显示了一条消息,
其中一部分是"I'm Poochie and I'm 5 years old "。

这部分消息表明aCopy从对象crit中正确的获得了一份数据成员值的副本,

消息的另一部分"&m_pName:0x22ff00"则表明,与crit的数据成员m_pName指向的字符串相比,有aCopy的数据成员m_pName指向的string对象存储在不同的内存块中,而前者的内存地址为"0x22ff20",这说明是深拷贝。

当testCopyConstructor()结束时,函数中使用的存储在变量aCopy中的Critter对象副本被销毁。析构函数释放掉堆中与副本相关的内存块,并且对main中的原始Critter对象不产生任何影响。
提示:当类的数据成员指向堆中内存时,应当考虑编写拷贝构造函数来为新对象分配内存,实现深拷贝。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息