分析 写时拷贝 的四个方案(Copy On Write)

深拷贝效率低,我们可以应引用计数的方式去解决浅拷贝中析构多次的问题。

首先要清楚写时拷贝是利用浅拷贝来解决问题!!

方案一

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

方案一最不靠谱,它将用作计数的整形变量_refCount定义为类的私有成员变量,任何一个对象都有它自己的成员变量_refCount,它们互不影响,只要拷贝出了对象,_refCount大于了1,那么每个对象调用自己的析构函数时--_refCount不等于0,那么它们指向的那块内存都将得不到释放,无法达到我们要的效果。

 1 //以下是对方案一的简单实现,大家可以结合上图感受到方案一的缺陷
 2
 3 #define _CRT_SECURE_NO_WARNINGS 1
 4
 5 #include<iostream>
 6 using namespace std;
 7 #include<assert.h>
 8
 9 class String
10 {
11 public:
12     String(char* str = "")    //不能strlen(NULL)
13        :_refCount(0)
14     {
15        _str = new char[strlen( str) + 1];
16        strcpy(_str, str);
17        _refCount++;
18     }
19     String(String &s)
20        :_refCount( s._refCount)
21     {
22        _str = s._str;
23        _refCount++;
24        s._refCount = _refCount;
25
26        //这里虽然可以让两个对象的_refCount相等,
27        //但如果超过两个对象的_str指针都指向同一块内存时,
28        //就无法让所有对象的_refCount都保持一致
29        //这是方案一的缺陷之一
30     }
31     ~String()
32     {
33        if (--_refCount == 0)
34        {
35             delete[] _str;
36            _str = NULL;
37            cout << "~String " << endl;
38        }
39     }
40     friend ostream& operator<<( ostream& output, const String &s);
41 private:
42     char* _str;
43     int _refCount;
44 };
45 ostream& operator<<( ostream& output, const String & s)
46 {
47     output << s._str;
48     return output;
49 }
50 void Test()
51 {
52     String s1("aaa");
53     String s2(s1);
54     String s3(s2);
55     cout << s1 << endl;
56     cout << s2 << endl;
57     cout << s3 << endl;
58 }
59 int main()
60 {
61     Test();
62     system("pause");
63     return 0;
64 }

方案一

方案二

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

设置一个静态整形变量来计算指向一块内存的指针的数量,每析构一次减1,直到它等于0(也就是没有指针在指向它的时候)再去释放那块内存,看似可行,其实不然!

这个方案只适用于只调用一次构造函数、只有一块内存的情形,如果多次调用构造函数构造对象,新构造的对象照样会改变count的值,那么以前的内存无法释放会造成内存泄漏。

结合上图和下面的代码,我们可以清楚地看到该方案相比方案一的改善,以及缺陷

 1 #define_CRT_SECURE_NO_WARNINGS 1
 2
 3
 4 #include<iostream>
 5 using namespace std;
 6 #include<assert.h>
 7
 8 class String
 9 {
10 public:
11     String(char* str = "")    //不能strlen(NULL)
12     {
13        _str = new char[strlen( str) + 1];
14        strcpy(_str, str);
15
16        count++;
17     }
18     String(const String &s)
19     {
20        _str = s._str;
21        count++;
22
23     }
24     String& operator=( String& s)
25     {
26        _str = s._str;
27        count++;
28        return *this;
29     }
30     ~String()
31     {
32        if (--count == 0)
33        {
34             delete[] _str;
35            _str = NULL;
36            cout << "~String " << endl;
37        }
38     }
39     friend ostream& operator<<( ostream& output, const String &s);
40     friend istream& operator>>( istream& input, const String &s);
41 private:
42     char* _str;
43     static int count;
44 };
45 ostream& operator<<( ostream& output, const String & s)
46 {
47     output << s._str;
48     return output;
49 }
50 istream& operator>>( istream& input, const String & s)
51 {
52     input >> s._str;
53     return input;
54 }
55
56 int String::count = 0;      //初始化count
57
58 void Test()
59 {
60     String s1("aaa");
61     String s2(s1);
62     String s3 = s2;
63     cout << s1 << endl;
64     cout << s2 << endl;
65     cout << s3 << endl;
66
67 }
68 int main()
69 {
70     Test();
71     system("pause");
72     return 0;
73 }

方案二

方案三

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

方案三设置了一个int型的指针变量用来引用计数,每份内存空间对应一个引用计数,而不是每个对象对应一个引用计数,而且每块内存的引用计数互不影响,不会出现方案一和方案二出现的问题。

1.在实现赋值运算符重载要谨慎,不要遇到下图的情形

 

2.改变字符串的某个字符时要谨慎,不要遇到类似下图所遇到的问题。

如果多个对象都指向同一块内存,那么只要一个对象改变了这块内存的内容,那所有的对象都被改变了!!

可以用下图的形式改善这种问题:新设置一块内存来存要改变的对象

案例3我画的图较多,方便大家结合代码去理解 

  1 #define _CRT_SECURE_NO_WARNINGS 1
  2
  3 #include<iostream>
  4 using namespace std;
  5 #include<assert.h>
  6
  7 class String
  8 {
  9 public:
 10     String(char* str = "")    //不能strlen(NULL)
 11     {
 12         _refCount = new int(1);     //给_refCount开辟空间,并赋初值1
 13         _size = strlen(str);
 14         _capacity = _size + 1;
 15         _str = new char[strlen(str) + 1];
 16         strcpy(_str, str);
 17     }
 18     String(const String &s)
 19     {
 20         _refCount = s._refCount;
 21         _str = s._str;
 22         _size = strlen(s._str);
 23         _capacity = _size + 1;
 24         (*_refCount)++;      //拷贝一次_refCount都要加1
 25
 26     }
 27
 28     //要考虑是s1=s2时,s1原先不为空的情况,要先释放原内存
 29     //如果要释放原内存时,要考虑它的_refCount减1后是否为0,为零再释放,否则其它对象指针无法再访问这片空间
 30     String& operator=(String& s)
 31     {
 32         if (_str!= s._str)
 33         {
 34             _size = strlen(s._str);
 35             _capacity = _size + 1;
 36             if (--(*_refCount) == 0)
 37             {
 38                 delete[] _str;
 39                 delete _refCount;
 40             }
 41
 42             _str = s._str;
 43             _refCount = s._refCount;
 44             (*_refCount)++;
 45         }
 46         return *this;
 47     }
 48     //如果修改了字符串的内容,那所有指向这块内存的对象指针的内容间接被改变
 49     //如果还有其它指针指向这块内存,我们可以从堆上重新开辟一块内存空间,
 50     //把原字符串拷贝过来
 51     //再去改变它的内容,就不会产生链式反应
 52     //  1.减引用计数  2.拷贝   3.创建新的引用计数
 53     char& String::operator[](const size_t index) //参考深拷贝
 54     {
 55         if (*_refCount==1)
 56         {
 57             return *(_str + index);
 58         }
 59         else
 60         {
 61             --(*_refCount);
 62             char* tmp = new char[strlen(_str)+1];
 63             strcpy(tmp, _str);
 64             _str = tmp;
 65             _refCount = new int(1);
 66             return *(_str+index);
 67         }
 68     }
 69     ~String()
 70     {
 71         if (--(*_refCount)== 0)  //当_refCount=0的时候就释放内存
 72         {
 73             delete[] _str;
 74             delete _refCount;
 75             _str = NULL;
 76             cout << "~String " << endl;
 77         }
 78         _size = 0;
 79         _capacity = 0;
 80     }
 81     friend ostream& operator<<(ostream& output, const String &s);
 82     friend istream& operator>>(istream& input, const String &s);
 83 private:
 84     char* _str;      //指向字符串的指针
 85     size_t  _size;      //字符串大小
 86     size_t  _capacity;   //容量
 87     int* _refCount;    //计数指针
 88 };
 89
 90
 91 ostream& operator<<(ostream& output, const String &s)
 92 {
 93     output << s._str;
 94     return output;
 95 }
 96 istream& operator>>(istream& input, const String &s)
 97 {
 98     input >> s._str;
 99     return input;
100 }
101
102 void Test()    //用例测试
103 {
104     String s1("abcdefg");
105     String s2(s1);
106     String s3;
107     s3 = s2;
108     cout << s1 << endl;
109     cout << s2 << endl;
110     cout << s3 << endl;
111     s2[3] = ‘0‘;
112     cout << s1 << endl;
113     cout << s2 << endl;
114     cout << s3 << endl;
115
116     //String s4("opqrst");
117     //String s5(s4);
118     //String s6 (s5);
119     //s6 = s4;
120     //cout << s4 << endl;
121     //cout << s5 << endl;
122     //cout << s6 << endl;
123
124 }
125 int main()
126 {
127     Test();
128     system("pause");
129     return 0;
130 }

方案三

方案四

class String
{
   private:
           char* _str;
};

方案四与方案三类似。方案四把用来计数的整形变量放在所开辟的内存空间的首部,

用*((int*)_str)就能取得计数值

  1 #define_CRT_SECURE_NO_WARNINGS 1
  2
  3 #include<iostream>
  4 using namespace std;
  5 #include<assert.h>
  6
  7 class String
  8 {
  9 public:
 10            String(char * str = "" )    //不能strlen(NULL)
 11            {
 12                     _str = new char[strlen( str) + 5];
 13                     _str += 4;
 14                     strcpy(_str, str);
 15                     GetRefCount(_str) = 1;
 16            }
 17            String(const String &s)
 18            {
 19                     _str = s._str;
 20                     ++GetRefCount(_str);
 21            }
 22
 23            //要考虑是s1=s2时,s1原先不为空的情况,要先释放原内存
 24            //如果要释放原内存时,要考虑它的_refCount减1后是否为0,
 25            //为零再释放,否则其它对象指针无法再访问这片空间
 26            String& operator=(String& s)
 27            {
 28                     if (this != &s )
 29                     {
 30                               if (GetRefCount(_str ) == 1)
 31                               {
 32                                        delete (_str-4);
 33                                        _str = s._str;
 34                                        ++GetRefCount(_str );
 35                               }
 36                               else
 37                               {
 38                                        --GetRefCount(_str );
 39                                        _str = s._str;
 40                                        ++GetRefCount(_str );
 41                               }
 42                     }
 43                     return *this ;
 44            }
 45            //如果修改了字符串的内容,那所有指向这块内存的对象指针的内容间接被改变
 46            //如果还有其它指针指向这块内存,我们可以从堆上重新开辟一块内存空间,
 47            //把原字符串拷贝过来.
 48            //再去改变它的内容,就不会产生链式反应
 49
 50
 51            char& String ::operator[](const size_t index ) //深拷贝
 52            {
 53
 54                               if (GetRefCount(_str) == 1)
 55                               {
 56                                        return _str[index ];
 57                               }
 58                               else
 59                               {
 60                                         //  1.减引用计数
 61                                        --GetRefCount(_str );
 62                                         //  2.拷贝     3.创建新的引用计数
 63                                        char* tmp = new char [strlen(_str) + 5];
 64                                       *((int *)tmp) = 1;
 65                                        tmp += 4;
 66                                        strcpy(tmp, _str);
 67                                        _str = tmp;
 68                                        return _str[index ];
 69                               }
 70            }
 71
 72            int& GetRefCount(char* ptr)    //获取引用计数(隐式内联函数)
 73            {
 74                     return *((int *)(ptr -4));
 75            }
 76            ~String()
 77            {
 78                     if (--GetRefCount(_str) == 0)
 79                     {
 80                               cout << "~String" << endl;
 81                               delete[] (_str-4);
 82                     }
 83
 84            }
 85            friend ostream& operator<<( ostream& output, const String &s);
 86            friend istream& operator>>( istream& input, const String &s);
 87 private:
 88            char* _str;
 89
 90 };
 91
 92
 93 ostream& operator<<(ostream& output, const String &s)
 94 {
 95            output << s._str;
 96            return output;
 97 }
 98 istream& operator>>(istream& input, const String &s)
 99 {
100            input >> s._str;
101            return input;
102 }
103
104 void Test()  //用例测试
105 {
106            String s1("abcdefg" );
107            String s2(s1);
108            String s3;
109            s3 = s2;
110            cout << s1 << endl;
111            cout << s2 << endl;
112            cout << s3 << endl;
113            s2[3] = ‘0‘;
114            cout << s1 << endl;
115            cout << s2 << endl;
116            cout << s3 << endl;
117
118            //String s4("opqrst");
119            //String s5(s4);
120            //String s6 (s5);
121            //s6 = s4;
122            //cout << s4 << endl;
123            //cout << s5 << endl;
124            //cout << s6 << endl;
125
126 }
127 int main()
128 {
129            Test();
130            system("pause" );
131            return 0;
132 }

方案四

时间: 2024-10-13 00:57:38

分析 写时拷贝 的四个方案(Copy On Write)的相关文章

写时拷贝(方案一)

深拷贝效率低,我们可以应引用计数的方式去解决浅拷贝中析构多次的问题. 首先要清楚写时拷贝是利用浅拷贝来解决问题!! 方案一 class String { private:     char* _str;     int _refCount; }; 方案一最不靠谱,它将用作计数的整形变量_refCount定义为类的私有成员变量,任何一个对象都有它自己的成员变量_refCount,它们互不影响,只要拷贝出了对象,_refCount大于了1,那么每个对象调用自己的析构函数时--_refCount不等于

Linux写时拷贝技术【转】

本文转载自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html COW技术初窥: 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程. 那么子进程的物理空间没有代码,怎么去取指令执行exec系统调用呢? 在fork之后exec之前两个进

Linux写时拷贝技术(copy-on-write)

COW技术初窥: 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程. 那么子进程的物理空间没有代码,怎么去取指令执行exec系统调用呢? 在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段.数据段.堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个.

【转】Linux写时拷贝技术(copy-on-write)

http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html 源于网上资料 COW技术初窥: 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程. 那么子进程的物理空间没有代码,怎么去取指令执行exec系统调用呢? 在fork之后exec之前两个

写时拷贝 引用计数器模型

1.深浅拷贝的使用时机: 浅拷贝:对只读数据共用一份空间,且只释放一次空间: 深拷贝:数据的修改,的不同空间: 2.引用计数器模型 使用变量use_count,来记载初始化对象个数: (1).static模型(此处只用浅拷贝与浅赋值) #include<iostream> #include<string.h> #include<malloc.h> using namespace std; class String{ public:     String(const ch

写时拷贝(copy-on-write) COW技术

时间:2014.05.06 地点:基地二楼 ---------------------------------------------------------------------------------- 一.写时拷贝的概念--COW技术在Linux进程上的应用 Linux在使用fork()函数进程创建时,传统fork()的做法是系统把所有的资源复制给新创建的进程,这种方式不仅单一,而且效率低下.因为所拷贝的数据或别的资源可能是可以共享的.现在Linux的fork()使用写时拷贝页来实现新进

深拷贝&amp;浅拷贝&amp;引用计数&amp;写时拷贝

(1).浅拷贝: class String { public: String(const char* str="") :_str(new char[strlen(str)+1]) { strcpy(_str,str); } ~String() { if(NULL!=_str) { delete[] _str; _str=NULL; } } private: char* _str; }; int main() { String s1("hello"); String

Linux写时拷贝技术(copy-on-write)

1.传统的fork()函数创建一个子进程,子进程和父进程共享正文段,复制数据段,堆,栈到子进程示意图如下: 2.Linux的fork()函数-写时复制(copy-on-write)创建一个子进程,内核只为子进程创建虚拟空间,不分配物理内存,和父进程共享物理空间,当父进程中有更改相应段的行为发生时,才为子进程分配物理空间.示意图如下: 3.vfork()函数创建一个子进程,共享父进程的一切.示意图如下: 4.传统fork与copy-on-write区别 传统的fork函数直接把所有资源复制给新的进

写时拷贝技术

Copy On Write(COW):写时拷贝技术 一.什么是写时拷贝技术: 写时拷贝技术可以理解为"写的时候才去分配空间",这实际上是一种拖延战术. 举个栗子: 二.写时拷贝技术原理: 写时拷贝技术是通过"引用计数"实现的,在分配空间的时候多分配4个字节,用来记录有多少个指针指向块空间,当有新的指针指向这块空间时,引用计数加一,当要释放这块空间时,引用计数减一(假装释放),直到引用计数减为0时才真的释放掉这块空间.当有的指针要改变这块空间的值时,再为这个指针分配自