zoukankan      html  css  js  c++  java
  • 行为像值的类 行为像指针的类

    通常,我们会按如下方式书写拷贝构造函数:

    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的时候,我们的构造函数只是简单的把l1lif成员的值赋予了l2lif,也就是说,它们保存的都是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);
    View Code

    注意到,在上面的拷贝构造函数中,我们为新对象的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 };
    View Code

    行为像指针的类

    即类提供的是浅拷贝,但由于可能有多个对象成员值相同一段内存,所以我们不能在析构时简单地释放资源。为了解决浅拷贝带来的野指针问题,需要引入一种技术——引用计数(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 }
    View Code
  • 相关阅读:
    杭电1009 FatMouse' Trade
    【HDU 3183】 字符串处理
    quick_sort
    【 HDU 3172 Virtual Friends】 并查集+map指针优化
    【HDU 3127】 完全背包
    【URAL 1260】 DP (dfs打表之后找规律也行)
    【 HDU 3038 How Many Answers Are Wrong】 并查集好题
    腾讯云的图片上传与下载
    获取ip(局域网内的IP是一样的)
    vue表单提交之后,清空input里的数据
  • 原文地址:https://www.cnblogs.com/wsl96/p/13289454.html
Copyright © 2011-2022 走看看