zoukankan      html  css  js  c++  java
  • String 类的实现(2)引用计数与写时拷贝

    1.引用计数 

      我们知道在C++中动态开辟空间时是用字符new和delete的。其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间。如图示其中保存N的值主要用于析构函数中析构对象的次数delete[] p时先取N(*((int*)p-1))。我们参照这种机制在实现String类的时候提供一个计数,将指向new开辟的空间的指针个数保存下来,当计数不小于或不等于0时不进行析构对象,也不释放空间。直到计数为0时释放空间。

    String的所有赋值、拷贝构造操作,计数器都会 +1 ; string 对象析构时,如果计数器为 0 则释放内存空间,否则计数器 -1 。实现代码如下

     1 //引用计数方法
     2 int my_strlen(const char *p)
     3 {
     4     int count = 0;
     5     assert(p);
     6     while (*p != '')
     7     {
     8         p++;
     9         count++;
    10     }
    11     return count;
    12 }
    13 char* my_strcopy(char* dest, const char* str)
    14 {
    15     assert(dest != NULL);
    16     assert(str != NULL);
    17     char* ret = dest;
    18     while (*dest++ = *str++)
    19     {
    20         ;
    21     }
    22     return ret;
    23 }
    24 class String
    25 {
    26 public:
    27     String(const char *pStr = "")
    28     {
    29         if (pStr == NULL)
    30         {
    31             _pStr = new char[1];
    32             *_pStr = '';
    33         }
    34         else
    35         {
    36             _pStr = new char[strlen(pStr) + 1];
    37             my_strcopy(_pStr, pStr);
    38         }
    39         _pCount = new int(1);
    40     }
    41     String(const String& s)
    42         :_pStr(s._pStr)
    43         ,_pCount(s._pCount)
    44     {
    45         _pStr++;
    46         *(_pCount)++;
    47     }
    48 
    49     ~String()
    50     {
    51         if (_pStr && (0 == --(*_pCount)))
    52         {
    53             delete[] _pStr;
    54             _pStr = NULL;
    55             delete[] _pCount;
    56             _pCount;
    57         }
    58     }
    59 
    60     String& operator=(const String& s)
    61     {
    62         if (this != &s)
    63         {
    64             if (_pStr && (0 == --(*_pCount)))
    65             {
    66                 delete[] _pStr;
    67                 delete[] _pCount;
    68             }
    69             _pStr = s._pStr;
    70             _pCount = s._pCount;
    71             --(*_pCount);
    72         }
    73         return *this;
    74     }
    75 
    76 private:
    77     char *_pStr;
    78     int *_pCount;
    79 };
    82 int main()
    83 {
    84     String s1;
    85     String s2 = "1234";
    86     String s3(s2);
    88     String s4;
    89     s4 = s2;
    90 }

     引用计数定义成类普通成员变量和静态成员变量(被static修饰)的优劣问题

        当类成员是静态时,它不属于类的任何一个对象,存在于任何一个对象之外,不由类的构造函数初始化,而对象的创建需要调用构造函数,所以它无法计数到正在使用同一块空间的对象的个数;对象中不包含任何与静态数据成员有关的数据,而我们的计数_Count就与对象绑定在一起;普通成员不可以是不完全类型;非静态成员不能作为默认实参,它的值本身属于对象的一部分。

    2.写时拷贝

     由于释放内存空间,开辟内存空间时花费时间,因此,在我们在不需要写,只是读的时候就可以不用新开辟内存空间,就用浅拷贝的方式创建对象,当我们需要写的时候才去新开辟内存空间。这种方法就是写时拷贝。这也是一种解决由于浅拷贝使多个对象共用一块内存地址,调用析构函数时导致一块内存被多次释放,导致程序奔溃的问题。这种方法同样需要用到引用计数:使用int *保存引用计数;采用所申请的4个字节空间。

     1 #include<iostream>  
     2 #include<stdlib.h>  
     3 using namespace std;
     4 class String
     5 {
     6 public:
     7     String(const char *pStr = "")
     8     {
     9         if (pStr == NULL)
    10         {
    11             _pStr = new char[1 + 4];
    12             *((int*)pStr) = 1;
    13             _pStr = (char*)(((int*)_pStr) + 1);
    14             *_pStr = '';
    15         }
    16         else
    17         {
    18             _pStr = new char[my_strlen(pStr) + 1 + 4];
    19             my_strcopy(_pStr, pStr);
    20             *((int*)_pStr - 1) = 1;
    21         }
    22     }
    23 
    24     String(const String& s)
    25         :_pStr(s._pStr)
    26     {
    27         ++GetCount();
    28     }
    29 
    30     ~String()
    31     {
    32         Release();
    33     }
    34 
    35     String& operator=(const String& s)
    36     {
    37         if (this != &s)
    38         {
    39             Release();
    40             _pStr = s._pStr;
    41             --(GetCount());
    42         }
    43         return *this;
    44     }
    45 
    46     char& operator[](size_t index)//写时拷贝
    47     {
    48         if (GetCount() > 1)      //当引用次数大于1时新开辟内存空间
    49         {
    50             char* pTem = new char[my_strlen(_pStr) + 1 + 4];
    51             my_strcopy(pTem + 4, _pStr);
    52             --GetCount();       //原来得空间引用计数器减1
    53             _pStr = pTem + 4;
    54             GetCount() = 1;
    55         }
    56         return _pStr[index];
    57     }
    58     const char& operator[](size_t index)const
    59     {
    60         return _pStr[index]; 
    61     }
    62    friend ostream& operator<<(ostream& output, const String& s)
    63    {
    64         output << s._pStr;
    65         return output;
    66    }
    67 private:
    68     int& GetCount()
    69     {
    70         return *((int*)_pStr - 1);
    71     }
    72     void Release()
    73     {
    74         if (_pStr && (0 == --GetCount()))
    75         {
    76             _pStr = (char*)((int*)_pStr - 1);
    77             delete _pStr;
    78         }
    79     }
    80 
    81     char *_pStr;
    82 };
    83 
    84 int main()
    85 {
    86     String s1;
    87     String s2 = "1234";
    88     String s3(s2);
    89     s2[0] = '5';
    90     String s4;
    91     s3 = s4;
    92 } 

       写时拷贝能减少不必要的内存操作,提高程序性能,但同时也是一把双刃剑,如果没按 stl 约定使用 String ,可能会导致极其严重的 bug ,而且通常是很隐蔽的,因为一般不会把注意力放到一个赋值语句。修改 String 数据时,先判断计数器是否为 1(为 1 代表没有其他对象共享内存空间),为 1 则可以直接使用内存空间(如上例中的 s2 ),否则触发写时拷贝,计数 -1 ,拷贝一份数据出来修改,并且新的内存计数器置 1 ; string 对象析构时,如果计数器为 1 则释放内存空间,否则计数也要 -1 。

    写时拷贝存在的线程安全问题

       线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。String类写时拷贝可能存在的问题详见:http://blog.csdn.net/haoel/article/details/24077

  • 相关阅读:
    Blank page instead of the SharePoint Central Administration site
    BizTalk 2010 BAM Configure
    Use ODBA with Visio 2007
    Handling SOAP Exceptions in BizTalk Orchestrations
    BizTalk与WebMethods之间的EDI交换
    Append messages in BizTalk
    FTP protocol commands
    Using Dynamic Maps in BizTalk(From CodeProject)
    Synchronous To Asynchronous Flows Without An Orchestration的简单实现
    WSE3 and "Action for ultimate recipient is required but not present in the message."
  • 原文地址:https://www.cnblogs.com/33debug/p/6661774.html
Copyright © 2011-2022 走看看