小结 | C++ String类的引用计数的浅拷贝、写时拷贝
来源:互联网 发布:电音和摇滚知乎 编辑:程序博客网 时间:2024/05/19 19:30
这篇博客将一步一步写出写实拷贝的的思路,以模拟实现string类为例子
1、深浅拷贝
#include <iostream>#include <stdlib.h>class String{public: String(const char* str = "") :_str(new char[strlen(str)+1]) { strcpy(_str, str); } //浅拷贝 String(const String&s) { _str = s._str; } ////深拷贝 //String(const String&s) // :_str(new char[strlen(s._str)+1]) //{ // strcpy(_str, s._str); //} ~String() { if (_str) { delete[] _str; } }private: char*_str;};int main(){ String s1("123455"); String s2(s1); return 0;}
上面的程序使用浅拷贝将会崩溃。因为浅拷贝是直接指向了同一块空间,在调用析构函数的时候会对同一块空间delete[]了两次。这样是不合法的。使用深拷贝将会没有任何问题。
使用了深拷贝,其实就是另外开辟一块空间,再将原来空间的值拷贝下来。
2、引用计数的浅拷贝
如果我们不想用深拷贝,因为深拷贝会开辟空间,这样浪费空间,同时还影响效率。那么我们就可以使用引用计数的浅拷贝。引用计数的浅拷贝指的是当一个类还有对象存在的时候,就不进行析构。这样就避免对同一块空间的多次delete[]。
(1)引入static _refcount当做计数器
class String{public: String(const char* str = "") :_str(new char[strlen(str)+1]) { strcpy(_str, str); } //浅拷贝 String(const String&s) { _str = s._str; _refcount++;//进行拷贝构造,增加引用计数的个数 } ~String() { if (--_refcount == 0) { delete[] _str; } }private: char*_str; static int _refcount;//定义为静态的};int String::_refcount = 1;//静态全局变量需要在类外面初始化
定义为静态的_refcount 是为了让对象共用一个。而不是每一个对象都有一个引用计数。
这样似乎是对的,但是一个类可以同时开辟出多个对象,这多个对象都有自己的空间,但是使用了静态的_refcount的话,他们都共有一个引用计数,这样依然会对同一块空间有多次的delete[]。
(2)使用指针类型的_refcount
为了克服以上的问题,我们打算开辟一个整型大小空间,用int* ——refcount
维护这块空间。这样就可以保证每一块空间搭配一个引用计数,同时同一个对象拷贝构造出来的使用的是同一个引用计数。
class String{public: String(const char* str = "") :_str(new char[strlen(str) + 1]) , _refcount(new int(1)) { strcpy(_str, str); } String(const String& s) :_str(s._str) { _refcount = s._refcount; (*_refcount)++; } //s1 = s2 String& operator=(const String& s) { if (_str != s._str) { if (--(*_refcount) == 0) { delete[] _str; delete _refcount; _str = NULL; _refcount = NULL; } _str = s._str; _refcount = s._refcount; ++(*_refcount); } return *this; } ~String() { if (--(*_refcount) == 0) { delete[] _str; delete _refcount; _str = NULL; _refcount = NULL; } }private: char* _str; int* _refcount;};int main(){ String s1("123455"); String s2(s1); String s3(s2); //创建新的空间 String s4("0009"); String s5(s4); return 0;}
s1\s2\s3指向同一块空间,共用一个引用计数。s4\s5指向另外一块空间,共用另一个引用计数
(3)将引用计数放到开辟空间的前面
使用指针的引用计数是一种好的方式,但是这样会增加内存空间的碎片化。为了解决内存碎片化,我们可以在开辟的_str 空间的前面多开一个int大小的空间,用作存放引用计数。模仿new[]的做法。
修改代码如下:
class String{public: String(const char* str = "") //strlrn(str) + 1是给'\0'留下的空间,+4是给引用计数 :_str(new char[strlen(str) + 1 + 4]) { *((int*)_str) = 1; //让_str指向引用计数之后的空间,即字符串的开始 _str += 4; strcpy(_str, str); } String(const String& s) //让_str指向引用计数 :_str(s._str-4) { //引用计数加一 (*((int*)_str))++; _str += 4; } //s1 = s2 String& operator=(const String& s) { if (_str != s._str) { //引用计数减一判0 if (--(*((int*)_str-1)) == 0) { delete[] _str; _str = NULL; } _str = s._str; //引用计数加一 (*((int*)s._str - 1))++; } return *this; } ~String() { if (--(*((int*)_str - 1)) == 0) { delete[] _str; _str = NULL; } }private: char* _str;};int main(){ String s1("123455"); String s2(s1); String s3(s2); String s4("0009"); String s5(s4); return 0;}
至此,我们的引用计数的浅拷贝已经完成的很好了。
3、写时拷贝
有一个问题,因为s1、s2同时指向了同一块空间,如果我对s1进行了值的修改,必定会同时修改了s2的值。我们不希望这事情的发生,因此我们引入了写时拷贝。写时拷贝的意思是,当我需要向空间写入值的时候,即修改值的时候,我们就将修改的对象拷贝一份出来。这样就不会影响到其他的对象了。
void CopyOnWrite(){ if (*((int*)_str-1) > 1) { (*((int*)_str - 1))--; char* tmp = new char[strlen(_str) + 5]; tmp -= 4; strcpy(tmp, _str); _str = tmp; (*((int*)_str+1))++; }}
- 小结 | C++ String类的引用计数的浅拷贝、写时拷贝
- String类的浅拷贝、深拷贝、引用计数、写时拷贝
- String-引用计数的写时拷贝
- string引用计数的写时拷贝
- C++::浅拷贝,深拷贝,引用计数的拷贝,写时拷贝
- string类的引用计数的写时拷贝分析
- string类的写时拷贝与引用计数
- String类引用计数的写时拷贝
- string类的写时拷贝与引用计数
- (String)引用计数写的拷贝
- String类引用计数的浅拷贝
- String类详解(浅拷贝,深拷贝,引用计数,写时拷贝)
- String类---深拷贝,简洁深拷贝,引用计数拷贝,写时拷贝
- 引用计数的写时拷贝
- 引用计数的写时拷贝
- 引用计数的写时拷贝
- 由深拷贝与浅拷贝引发的引用计数、写时拷贝技术
- 由深拷贝与浅拷贝引发的引用计数、写时拷贝技术
- MySQL-InnoDB
- java的三大特性之封装
- Kinect V2开发(6)骨骼数据平滑处理
- 编译原理学习笔记1
- JAVA多线程实现的三种方式
- 小结 | C++ String类的引用计数的浅拷贝、写时拷贝
- java中Collections.sort排序详解
- 【SpringBoot】2小时学会SpringBoot学习笔记( 第6章 事务管理 )
- mysql/oracle字符串比较大小
- Linux线程
- Ubuntu更改默认python版本的两种方法 python-> Anaconda
- 左手/右手坐标系
- java的三大特性之继承
- 「Deep Learning」理解Pytorch中的「torch.utils.data」