深拷贝效率低,我们可以应引用计数的方式去解决浅拷贝中析构多次的问题。
首先要清楚写时拷贝是利用浅拷贝来解决问题!!
方案一
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