行为像值的类 行为像指针的类
通常,我们会按如下方式书写拷贝构造函数:
1 class LiF {
2 public:
3 LiF(int _lif = 0) : lif(_lif) {} // 默认构造函数
4 LiF(const LiF& l) : lif(l.lif) {} // 拷贝构造函数
5 private:
6 int lif;
7 };
这是正确的。但是,如果数据成员包含指针类型的话,这种写法就很危险了。
1 class LiF {
2 public:
3 LiF() { lif = new int(0); } // 为lif动态分配内存
4 LiF(const LiF& l) : lif(l.lif) {} // 拷贝构造函数
5 ~LiF() { // 析构函数
6 delete lif; // 释放分配给lif的资源
7 lif = nullptr; // 置空
8 }
9 private:
10 int* lif;
11 };
12
13 LiF l1;
14 LiF l2(l1); // 程序结束析构l2时,程序将崩溃
在拷贝l1
生成l2
的时候,我们的构造函数只是简单的把l1
的lif
成员的值赋予了l2
的lif
,也就是说,它们保存的都是l1
构造时分配的地址,当两者之中的某个对象被销毁时,构造函数正常执行,资源被释放,但之后如果另一个对象也被析构,lif
的资源就会被重复释放,lif
也就变成野指针。这种拷贝方式也称为浅拷贝,即只拷贝空间,不拷贝资源。
更改为:
1 class LiF { 2 public: 3 LiF() { lif = new int(0); } // 为lif动态分配内存 4 LiF(const LiF& l) : lif(new int(*l.lif)) {} // 深拷贝构造函数 5 ~LiF() { // 析构函数 6 delete lif; // 释放分配给lif的资源 7 lif = nullptr; // 置空 8 } 9 private: 10 int* lif; 11 }; 12 13 LiF l1; 14 LiF l2(l1);
注意到,在上面的拷贝构造函数中,我们为新对象的lif
成员分配了一块新的内存,即完成了深拷贝。
行为像值的类
即类提供的构造函数是深拷贝,类的每个对象都有自己的一份拷贝。对于这样的类,它显然需要:一个深拷贝构造函数、一个深拷贝赋值运算符重载、一个可以释放成员占用的资源的析构函数。
1 class HasPtr 2 { 3 string *ps; 4 int i; 5 public: 6 HasPtr(const string &s = string()): ps(new string(s)), i(0) {} 7 HasPtr(HasPtr &hp) : ps(new string(*hp.ps)), i(hp.i) {} 8 HasPtr& operator=(const HasPtr &hp) 9 { 10 delete ps; 11 ps = new string(*hp.ps); 12 i = hp.i; 13 return *this; 14 } 15 ~HasPtr() 16 { 17 delete ps; 18 } 19 };
行为像指针的类
即类提供的是浅拷贝,但由于可能有多个对象成员值相同一段内存,所以我们不能在析构时简单地释放资源。为了解决浅拷贝带来的野指针问题,需要引入一种技术——引用计数(reference count)。这也是C++11的智能指针shared_ptr的实现。
引用计数:
- 在每个构造函数初始化对象时,额外创建一个引用计数并置为1,用以记录有多少对象正在共享资源。
- 在执行拷贝操作时进行浅拷贝,同时拷贝计数器,并递增计数器,指出共享的对象增加了一个。
- 在进行拷贝赋值时比较特殊但也很容易理解:需要递增右侧对象的计数器并递减左侧对象的计数器。若左侧对象引用计数归零,则释放资源。
- 在析构对象时,并不直接释放共享的资源,而是递减计数器,直至计数器归零才释放资源。
1 class HasPtr 2 { 3 public: 4 //默认构造函数 5 HasPtr(const string &s = string()): ps(new string(s)), i(0), use(new size_t(1)) {} 6 //拷贝构造函数,完成string 指针指向内容的拷贝和i值的拷贝 7 HasPtr(const HasPtr &hp): ps(hp.ps), i(hp.i), use(hp.use) { ++*use; } 8 //拷贝赋值运算符 9 HasPtr& operator=(const HasPtr &); 10 //析构函数 11 ~HasPtr(); 12 private: 13 string *ps; 14 int i; 15 size_t *use; 16 }; 17 18 HasPtr& HasPtr::operator=(const HasPtr &p) 19 { 20 ++*p.use; 21 if(--*use == 0) { 22 delete ps; 23 delete use; 24 } 25 ps = p.ps; 26 i = p.i; 27 use = p.use; 28 return *this; 29 } 30 31 HasPtr::~HasPtr() 32 { 33 if(--*use == 0) { 34 delete ps; 35 delete use; 36 } 37 }