一般,咱们会按以下方式书写拷贝构造函数:函数
class LiF { public: LiF(int _lif = 0) : lif(_lif) {} // 默认构造函数 LiF(const LiF& l) : lif(l.lif) {} // 拷贝构造函数 private: int lif; };
这是正确的。可是,若是数据成员包含指针类型的话,这种写法就很危险了。this
class LiF { public: LiF() { lif = new int(0); } // 为lif动态分配内存 LiF(const LiF& l) : lif(l.lif) {} // 拷贝构造函数 ~LiF() { // 析构函数 delete lif; // 释放分配给lif的资源 lif = nullptr; // 置空 } private: int* lif; }; LiF l1; LiF l2(l1); // 程序结束析构l2时,程序将崩溃
在拷贝l1
生成l2
的时候,咱们的构造函数只是简单的把l1
的lif
成员的值赋予了l2
的lif
,也就是说,它们保存的都是l1
构造时分配的地址,当二者之中的某个对象被销毁时,构造函数正常执行,资源被释放,但以后若是另外一个对象也被析构,lif
的资源就会被重复释放,lif
也就变成野指针。这种拷贝方式也称为浅拷贝,即只拷贝空间,不拷贝资源。指针
为了防止指针类型的数据成员出现野指针错误,对应地便有了深拷贝操做,即在拷贝对象内容的同时为拷贝的内容分配新的资源。code
class LiF { public: LiF() { lif = new int(0); } // 为lif动态分配内存 LiF(const LiF& l) : lif(new int(*l.lif)) {} // 深拷贝构造函数 ~LiF() { // 析构函数 delete lif; // 释放分配给lif的资源 lif = nullptr; // 置空 } private: int* lif; }; LiF l1; LiF l2(l1);
注意到,在上面的拷贝构造函数中,咱们为新对象的lif
成员分配了一块新的内存,即完成了深拷贝。对象
从上面的例子能够获得两种抽象的类行为:行为像值、行为像指针。内存
即类提供的构造函数是深拷贝,类的每一个对象都有本身的一份拷贝。对于这样的类,它显然须要:一个深拷贝构造函数、一个深拷贝赋值运算符重载、一个能够释放成员占用的资源的析构函数。资源
class LiF { public: LiF(const int& _lif = 0): lif(new int(_lif)) {} LiF(const LiF& l) : lif(new int(*l.lif)) {} LiF& operator= (const LiF& l) { lif = new int(*l.lif); return *this; } ~LiF() { delete lif; lif = nullptr; } private: int* lif; };
即类提供的是浅拷贝,但因为可能有多个对象成员值相同一段内存,因此咱们不能在析构时简单地释放资源。为了解决浅拷贝带来的野指针问题,须要引入一种技术——引用计数(reference count)。这也是C++11的智能指针shared_ptr的实现。class
引用计数:构造函数
class LiF { public: LiF(const int& _lif = 0): lif(new int(_lif)), referenceCount(new unsigned(1)) {} LiF(const LiF& l) : lif(l.lif), referenceCount(l.referenceCount) { ++ *referenceCount; } LiF& operator= (const LiF& l) { ++ *l.referenceCount; if (-- *referenceCount == 0) { delete lif; delete referenceCount; } lif = l.lif; referenceCount = l.referenceCount; return *this; } ~LiF() { if (-- *referenceCount == 0) { delete lif; delete referenceCount; } } private: int *lif; unsigned *referenceCount; };