歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

由淺入深地分析 寫時拷貝(Copy On Write)

本文旨在通過對 寫時拷貝 的四個方案(Copy On Write)分析,讓大家明白寫時拷貝的實現及原理。

深拷貝效率低,我們可以應引用計數的方式去解決淺拷貝中析構多次的問題。

首先要清楚寫時拷貝是利用淺拷貝來解決問題!!

方案一

class String
{
private:
    char* _str;
    int _refCount;
};

方案一最不靠譜,它將用作計數的整形變量_refCount定義為類的私有成員變量,任何一個對象都有它自己的成員變量_refCount,它們互不影響,只要拷貝出了對象,_refCount大於了1,那麼每個對象調用自己的析構函數時--_refCount不等於0,那麼它們指向的那塊內存都將得不到釋放,無法達到我們要的效果。

//以下是對方案一的簡單實現,大家可以結合上圖感受到方案一的缺陷
 
#define _CRT_SECURE_NO_WARNINGS 1
 
#include<iostream>
using namespace std;
#include<assert.h>
 
class String
{
public:
    String(char* str = "")    //不能strlen(NULL)
      :_refCount(0)
    {
      _str = new char[strlen( str) + 1];
      strcpy(_str, str);
      _refCount++;
    }
    String(String &s)
      :_refCount( s._refCount)   
    {
      _str = s._str;
      _refCount++;
      s._refCount = _refCount;
       
      //這裡雖然可以讓兩個對象的_refCount相等,
      //但如果超過兩個對象的_str指針都指向同一塊內存時,
      //就無法讓所有對象的_refCount都保持一致
      //這是方案一的缺陷之一
    }
    ~String()
    {
      if (--_refCount == 0)
      {
            delete[] _str;
          _str = NULL;
          cout << "~String " << endl;
      }
    }
    friend ostream& operator<<( ostream& output, const String &s);
private:
    char* _str;
    int _refCount;
};
ostream& operator<<( ostream& output, const String & s)
{
    output << s._str;
    return output;
}
void Test()
{
    String s1("aaa");
    String s2(s1);
    String s3(s2);
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
}
int main()
{
    Test();
    system("pause");
    return 0;
}

方案二

class String
{
private:
    char* _str;
    static int count;
};

設置一個靜態整形變量來計算指向一塊內存的指針的數量,每析構一次減1,直到它等於0(也就是沒有指針在指向它的時候)再去釋放那塊內存,看似可行,其實不然!

這個方案只適用於只調用一次構造函數、只有一塊內存的情形,如果多次調用構造函數構造對象,新構造的對象照樣會改變count的值,那麼以前的內存無法釋放會造成內存洩漏。

結合上圖和下面的代碼,我們可以清楚地看到該方案相比方案一的改善,以及缺陷

#define_CRT_SECURE_NO_WARNINGS 1
 
 
#include<iostream>
using namespace std;
#include<assert.h>
 
class String
{
public:
    String(char* str = "")    //不能strlen(NULL)
    {
      _str = new char[strlen( str) + 1];
      strcpy(_str, str);
 
      count++;
    }
    String(const String &s)
    {
      _str = s._str;
      count++;
       
    }
    String& operator=( String& s) 
    {
      _str = s._str;
      count++;
      return *this;
    }
    ~String()
    {
      if (--count == 0)
      {
            delete[] _str;
          _str = NULL;
          cout << "~String " << endl;
      }
    }
    friend ostream& operator<<( ostream& output, const String &s);
    friend istream& operator>>( istream& input, const String &s);
private:
    char* _str;
    static int count;
};
ostream& operator<<( ostream& output, const String & s)
{
    output << s._str;
    return output;
}
istream& operator>>( istream& input, const String & s)
{
    input >> s._str;
    return input;
}
 
int String::count = 0;      //初始化count
 
void Test()
{
    String s1("aaa");
    String s2(s1);
    String s3 = s2;
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
 
}
int main()
{
    Test();
    system("pause");
    return 0;
}

方案三

class String
{
    private:
              char* _str;
              int* _refCount;     
};

方案三設置了一個int型的指針變量用來引用計數,每份內存空間對應一個引用計數,而不是每個對象對應一個引用計數,而且每塊內存的引用計數互不影響,不會出現方案一和方案二出現的問題。

1.在實現賦值運算符重載要謹慎,不要遇到下圖的情形

2.改變字符串的某個字符時要謹慎,不要遇到類似下圖所遇到的問題。

如果多個對象都指向同一塊內存,那麼只要一個對象改變了這塊內存的內容,那所有的對象都被改變了!!

可以用下圖的形式改善這種問題:新設置一塊內存來存要改變的對象

案例3我畫的圖較多,方便大家結合代碼去理解

#define _CRT_SECURE_NO_WARNINGS 1
 
#include<iostream>
using namespace std;
#include<assert.h>
 
class String
{
public:
    String(char* str = "")    //不能strlen(NULL)
    {
        _refCount = new int(1);    //給_refCount開辟空間,並賦初值1
        _size = strlen(str);
        _capacity = _size + 1;
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    String(const String &s)
    {
        _refCount = s._refCount;
        _str = s._str;
        _size = strlen(s._str);
        _capacity = _size + 1;
        (*_refCount)++;      //拷貝一次_refCount都要加1
       
    }
   
    //要考慮是s1=s2時,s1原先不為空的情況,要先釋放原內存
    //如果要釋放原內存時,要考慮它的_refCount減1後是否為0,為零再釋放,否則其它對象指針無法再訪問這片空間
    String& operator=(String& s) 
    {
        if (_str!= s._str)
        {
            _size = strlen(s._str);
            _capacity = _size + 1;
            if (--(*_refCount) == 0)
            {
                delete[] _str;
                delete _refCount;
            }
           
            _str = s._str;
            _refCount = s._refCount;
            (*_refCount)++;
        }     
        return *this;
    }
    //如果修改了字符串的內容,那所有指向這塊內存的對象指針的內容間接被改變
    //如果還有其它指針指向這塊內存,我們可以從堆上重新開辟一塊內存空間,
    //把原字符串拷貝過來
    //再去改變它的內容,就不會產生鏈式反應
    //  1.減引用計數  2.拷貝  3.創建新的引用計數
    char& String::operator[](const size_t index) //參考深拷貝     
    {
        if (*_refCount==1)
        {
            return *(_str + index);
        }
        else
        {
            --(*_refCount);
            char* tmp = new char[strlen(_str)+1];
            strcpy(tmp, _str);
            _str = tmp;
            _refCount = new int(1);
            return *(_str+index);
        }
    }
    ~String()
    {
        if (--(*_refCount)== 0)  //當_refCount=0的時候就釋放內存
        {
            delete[] _str;
            delete _refCount;
            _str = NULL;
            cout << "~String " << endl;
        }
        _size = 0;
        _capacity = 0;
    }
    friend ostream& operator<<(ostream& output, const String &s);
    friend istream& operator>>(istream& input, const String &s);
private:
    char* _str;      //指向字符串的指針
    size_t  _size;      //字符串大小
    size_t  _capacity;  //容量
    int* _refCount;    //計數指針
};
 
 
ostream& operator<<(ostream& output, const String &s)
{
    output << s._str;
    return output;
}
istream& operator>>(istream& input, const String &s)
{
    input >> s._str;
    return input;
}
 
void Test()    //用例測試
{
    String s1("abcdefg");
    String s2(s1);
    String s3;
    s3 = s2;
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
    s2[3] = '0';
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
 
    //String s4("opqrst");
    //String s5(s4);
    //String s6 (s5);
    //s6 = s4;
    //cout << s4 << endl;
    //cout << s5 << endl;
    //cout << s6 << endl;
 
}
int main()
{
    Test();
    system("pause");
    return 0;
}

方案四

class String

{

  private:

          char* _str;

};

方案四與方案三類似。方案四把用來計數的整形變量放在所開辟的內存空間的首部,

用*((int*)_str)就能取得計數值

#define_CRT_SECURE_NO_WARNINGS 1
 
#include<iostream>
using namespace std;
#include<assert.h>
 
class String
{
public:
          String(char * str = "" )    //不能strlen(NULL)
          {
                    _str = new char[strlen( str) + 5];
                    _str += 4;
                    strcpy(_str, str);
                    GetRefCount(_str) = 1;
          }
          String(const String &s)
          {
                    _str = s._str;
                    ++GetRefCount(_str);
          }
 
          //要考慮是s1=s2時,s1原先不為空的情況,要先釋放原內存
          //如果要釋放原內存時,要考慮它的_refCount減1後是否為0,
          //為零再釋放,否則其它對象指針無法再訪問這片空間
          String& operator=(String& s)
          {
                    if (this != &s )
                    {
                              if (GetRefCount(_str ) == 1)
                              {
                                      delete (_str-4);
                                      _str = s._str;
                                      ++GetRefCount(_str );
                              }
                              else
                              {
                                      --GetRefCount(_str );
                                      _str = s._str;
                                      ++GetRefCount(_str );
                              }
                    }
                    return *this ;
          }
          //如果修改了字符串的內容,那所有指向這塊內存的對象指針的內容間接被改變
          //如果還有其它指針指向這塊內存,我們可以從堆上重新開辟一塊內存空間,
          //把原字符串拷貝過來.
          //再去改變它的內容,就不會產生鏈式反應
           
         
          char& String ::operator[](const size_t index ) //深拷貝   
          {
                   
                              if (GetRefCount(_str) == 1)
                              {
                                      return _str[index ];
                              }
                              else
                              {
                                        //  1.減引用計數
                                      --GetRefCount(_str );
                                        //  2.拷貝    3.創建新的引用計數
                                      char* tmp = new char [strlen(_str) + 5]; 
                                      *((int *)tmp) = 1;
                                      tmp += 4;
                                      strcpy(tmp, _str);
                                      _str = tmp;
                                      return _str[index ];
                              }
          }
 
          int& GetRefCount(char* ptr)    //獲取引用計數(隱式內聯函數)
          {
                    return *((int *)(ptr -4));
          }
          ~String()
          {
                    if (--GetRefCount(_str) == 0)
                    {
                              cout << "~String" << endl;
                              delete[] (_str-4);           
                    }
         
          }
          friend ostream& operator<<( ostream& output, const String &s);
          friend istream& operator>>( istream& input, const String &s);
private:
          char* _str;
 
};
 
 
ostream& operator<<(ostream& output, const String &s)
{
          output << s._str;
          return output;
}
istream& operator>>(istream& input, const String &s)
{
          input >> s._str;
          return input;
}
 
void Test()  //用例測試
{
          String s1("abcdefg" );
          String s2(s1);
          String s3;
          s3 = s2;
          cout << s1 << endl;
          cout << s2 << endl;
          cout << s3 << endl;
          s2[3] = '0';
          cout << s1 << endl;
          cout << s2 << endl;
          cout << s3 << endl;
 
          //String s4("opqrst");
          //String s5(s4);
          //String s6 (s5);
          //s6 = s4;
          //cout << s4 << endl;
          //cout << s5 << endl;
          //cout << s6 << endl;
 
}
int main()
{
          Test();
          system("pause" );
          return 0;
}

Copyright © Linux教程網 All Rights Reserved