9.4 派生类中的复制构造函数(2)
试一试:派生类中的复制构造函数
利用下面这个示例,我们可以试用一下刚刚定义的复制构造函数。
- // Ex9_05.cpp
- // Using a derived class copy constructor
- #include <iostream> // For stream I/O
- #include <cstring> // For strlen() and strcpy()
- #include "CandyBox.h" // For CBox and CCandyBox
- using std::cout;
- using std::endl;
-
- int main()
- {
- CCandyBox chocBox(2.0, 3.0, 4.0, "Chockies");
// Declare and initialize - CCandyBox chocolateBox(chocBox); // Use copy constructor
-
- cout << endl
- << "Volume of chocBox is " << chocBox.Volume()
- << endl
- << "Volume of chocolateBox is " << chocolateBox.Volume()
- << endl;
-
- return 0;
- }
示例说明(不能工作的原因)
当我们运行该示例的Debug版本时,除了预期的输出以外,还会看到一个如图9-4所示的对话框。
|
| (点击查看大图)图 9-4 |
单击Abort按钮清除该对话框,我们将在控制台窗口中看到预期的输出。输出表明,编译器为派生类生成的复制构造函数自动调用了基类的复制构造函数。
但正如您可能已经意识到的那样,一切都不是理所应当的那样。在这个特定的示例中,编译器生成的复制构造函数导致了问题的出现,因为声明的第二个派生类对象的m_Contents成员指向了第一个对象的m_Contents成员指向的相同内存。当其中一个对象在main()结束时因离开作用域而被销毁时,将释放字符串占用的内存。当销毁第二个对象时,析构函数企图释放已经因调用先前的对象而被释放的内存,这就是上面的对话框中出现出错消息的原因。
修正该问题的方法是为派生类提供一个复制构造函数,以便为新对象另外分配内存。
试一试:修正复制构造函数的问题
为此,我们可以给Ex9_05文件中派生类CCandyBox的public部分添加下面的复制构造函数代码:
- // Derived class copy constructor
- CCandyBox(const CCandyBox& initCB)
- {
- cout << endl << "CCandyBox copy constructor called";
-
- // Get new memory
- m_Contents = new char[ strlen(initCB.m_Contents) + 1 ];
-
- // Copy string
- strcpy_s(m_Contents,strlen(initCB.m_Contents) + 1, initCB.m_Contents);
- }
我们现在可以用相同的main()函数,重新运行经过修改的上一个示例,以了解新版复制构造函数的工作原理。
示例说明
当再次运行该示例时,程序表现出更好的行为,并产生下面的输出:
- CBox constructor called
- CCandyBox constructor2 called
- CBox constructor called
- CCandyBox copy constructor called
- Volume of chocBox is 24
- Volume of chocolateBox is 1
- CCandyBox destructor called
- CBox destructor called
- CCandyBox destructor called
- CBox destructor called
但该程序仍然有错误。第三行输出显示,被调用的是chocolateBox对象CBox部分的默认构造函数,而非复制构造函数。因此,该对象得到默认的尺寸而非初始化对象的尺寸,那么其体积值自然是错误的。原因在于当我们编写派生类的构造函数时,必须确保派生类对象的成员被正确初始化,其中当然包括继承的成员。
修正方法是在CCandyBox类复制构造函数的初始化列表中,调用基类部分的复制构造函数。修改后的函数如下所示:
- // Derived class copy constructor
- CCandyBox(const CCandyBox& initCB): CBox(initCB)
- {
- cout << endl << "CCandyBox copy constructor called";
-
- // Get new memory
- m_Contents = new char[ strlen(initCB.m_Contents) + 1 ];
-
- // Copy string
- strcpy_s(m_Contents, strlen(initCB.m_Contents)+1, initCB.m_Contents);
- }
现在,我们以对象initCB为实参调用了CBox类的复制构造函数。传递的只是该对象的基类部分,因此一切问题都将解决。如果我们通过添加对基类复制构造函数的调用对上一个示例进行修改,则输出将如下所示:
- CBox constructor called
- CCandyBox constructor2 called
- CBox copy constructor called
- CCandyBox copy constructor called
- Volume of chocBox is 24
- Volume of chocolateBox is 24
- CCandyBox destructor called
- CBox destructor called
- CCandyBox destructor called
- CBox destructor called
输出显示,所有构造函数和析构函数都是以正确的顺序被调用的,chocolateBox中CBox部分的复制构造函数先于CCandyBox类的复制构造函数被调用。派生类对象chocolateBox的体积现在与其初始化对象相同,事情本来就应该是这样。
因此,我们又得到一条需要牢记的黄金法则:
在为派生类编写构造函数时,需要初始化包括继承成员在内的派生类对象的所有成员。