尽管有默认的复制构造函数来解决一般对象与对象之间的初始化问题, 但是在有些情况下我们必须手动显式的去定义复制构造函数, 例如:
1 #include <iostream> 2 #include <cstring> 3 4 using namespace std; 5 6 class Book 7 { 8 public: 9 Book( const char *name ) 10 { 11 bookName = new char [strlen(name)+1]; //使用 new 申请 strlen(name)+1 大小的空间 12 strcpy(bookName, name); 13 } 14 ~Book() { delete []bookName; } //释放申请的空间 15 void showName(){ cout<<"Book name: " << bookName << endl; } 16 17 private: 18 char *bookName; 19 }; 20 21 int main() 22 { 23 Book CPP("C++ Primer"); 24 Book T(CPP); //使用 CPP 初始化对象 T 25 CPP.showName(); 26 CPP.~Book(); //手动释放对象CPP所申请的空间 27 28 T.showName(); 29 30 return 0; 31 }
编译运行的结果:
Book name: C++ Primer Book name: Process returned 0 (0x0) execution time : 0.281 s Press any key to continue.
按照前面的思路, 使用 CPP 对象对 T 对象进行初始化后, 那么 T 对象的 bookName 属性理论上来说也是 "C++ Primer", 但是从输出结果来看在输出 CPP 对象的 bookName 属性时是正常的, 而 T 对象的 bookName 输出有问题, 正确的情况下应该也是 "C++ Primer", 不过此时输出的却是空白。
这正是构造函数的一点不足之处, 造成这种现象的原因在于 CPP.~Book(); 这行, 还原下该默认复制构造函数的实现:
Book( const Book &obj )
{
bookName = obj.bookName;
}
可以看到, 实际上当用 CPP 对象来初始化 T 对象时, 默认复制构造函数只是简单的将 CPP 对象的 bookName 赋值给 T 对象的 bookName, 换句话说, 也就是只是将 CPP 对象的 bookName 指向的空间地址赋值给 T 的 bookName, 这样一来, T 对象的 bookName 和 CPP 对象的 bookName 就是指向同一处内存单元, 当 CPP 对象调用析构函数后, CPP 的 bookName 所指向的内存单元就会被释放, 由于 T 对象 bookName 与 CPP 对象的 bookName 指向的是同一处内存, 所以此时 T 对象的 bookName 指向的内存就变成了一处不可用的非法内容(因为已经释放), 所以在指向的内存被释放的情况下进行输出势必会造成了输出的错误。
一般来说, 当类中含有指针型的数据成员、需要使用动态内存的, 最好手动显式定义复制构造函数来避免该问题。
三、显式定义复制构造函数
显式定义复制构造函数的步骤非常简单, 只要记得函数的参数是 本类成员的引用 就行, 虽然也可以通过指针来实现, 但是不推荐这样做, 指针在某种程度上来说要比引用危险。显式定义复制构造函数解决上例中的问题:
1 #include <iostream> 2 #include <cstring> 3 4 using namespace std; 5 6 class Book 7 { 8 public: 9 Book( const char *name ) 10 { 11 bookName = new char [strlen(name)+1]; 12 strcpy(bookName, name); 13 } 14 Book( const Book &obj ) //显式定义复制构造函数 15 { 16 bookName = new char [strlen(obj.bookName)+1]; //调用复制构造函数时再次申请一处新的空间 17 strcpy(bookName, obj.bookName); 18 } 19 ~Book() { delete []bookName; } //释放申请的空间 20 void showName(){ cout<<"Book name: " << bookName << endl; } 21 22 private: 23 char *bookName; 24 }; 25 26 int main() 27 { 28 Book CPP("C++ Primer"); 29 Book T = CPP; //使用 CPP 初始化对象 T 30 CPP.showName(); 31 CPP.~Book(); //手动释放对象CPP所申请的空间 32 33 T.showName(); 34 35 return 0; 36 }
编译运行的结果:
Book name: C++ Primer Book name: C++ Primer Process returned 0 (0x0) execution time : 0.281 s Press any key to continue.
在该示例中我们显式定义了复制构造函数来代替默认复制构造函数, 在该复制构造函数的函数体内, 不是再直接将源对象所申请空间的地址赋值给被初始化的对象, 而是自己独立申请一处内存后再将源对象的属性复制过来, 此时 CPP 对象的 bookName 与 T 对象的 bookName 就是指向两处不同的内存单元, 这样即便是源对象 CPP 被销毁后被初始化的对象 T 也不会再受到影响。