String 类的实现(3)引用计数实现String类
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 。实现代码如下
//引用计数方法
int my_strlen(const char *p)
{
int count = ;
assert(p);
while (*p != '\0')
{
p++;
count++;
}
return count;
}
char* my_strcopy(char* dest, const char* str)
{
assert(dest != NULL);
assert(str != NULL);
char* ret = dest;
while (*dest++ = *str++)
{
;
}
return ret;
}
class String
{
public:
String(const char *pStr = "")
{
if (pStr == NULL)
{
_pStr = new char[];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr) + ];
my_strcopy(_pStr, pStr);
}
_pCount = new int();
}
String(const String& s)
:_pStr(s._pStr)
,_pCount(s._pCount)
{
_pStr++;
*(_pCount)++;
} ~String()
{
if (_pStr && ( == --(*_pCount)))
{
delete[] _pStr;
_pStr = NULL;
delete[] _pCount;
_pCount;
}
} String& operator=(const String& s)
{
if (this != &s)
{
if (_pStr && ( == --(*_pCount)))
{
delete[] _pStr;
delete[] _pCount;
}
_pStr = s._pStr;
_pCount = s._pCount;
--(*_pCount);
}
return *this;
} private:
char *_pStr;
int *_pCount;
};
int main()
{
String s1;
String s2 = "";
String s3(s2);
String s4;
s4 = s2;
}
引用计数定义成类普通成员变量和静态成员变量(被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 = '\0';
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
String 类的实现(3)引用计数实现String类的更多相关文章
- (转)C++——std::string类的引用计数
1.概念 Scott Meyers在<More Effective C++>中举了个例子,不知你是否还记得?在你还在上学的时候,你的父母要你不要看电视,而去复习功课,于是你把自己关在房间里 ...
- String 类的实现(2)引用计数与写时拷贝
1.引用计数 我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N ...
- C++ 引用计数技术及智能指针的简单实现
一直以来都对智能指针一知半解,看C++Primer中也讲的不够清晰明白(大概是我功力不够吧).最近花了点时间认真看了智能指针,特地来写这篇文章. 1.智能指针是什么 简单来说,智能指针是一个类,它对普 ...
- 【M29】引用计数
1.引用计数这项技术,是为了让等值对象对象共享同一实体.此技术的发展有两个动机:a.记录堆上分配的对象,是垃圾回收机制的简单原理:b.节省内存,多个对象具有相同的值,存储多次很笨.速度更快,等值对象避 ...
- 【转载】C++应用引用计数技术
原帖:http://www.cnblogs.com/chain2012/archive/2010/11/12/1875578.html 因为Windows的内核对象也运用了引用计数,所以稍作了解并非无 ...
- swift学习笔记(七)自己主动引用计数
与Object-c一样,swift使用自己主动引用计数来跟踪并管理应用使用的内存.当实例不再被使用时,及retainCount=0时,会自己主动释放是理所占用的内存空间. 注:引用计数仅适用于类的实例 ...
- Swift5 语言指南(二十五) 自动引用计数(ARC)
Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用情况.在大多数情况下,这意味着内存管理在Swift中“正常工作”,您不需要自己考虑内存管理.当不再需要这些实例时,ARC会自动释放类实 ...
- Objective-C内存管理之引用计数
初学者在学习Objective-c的时候,很容易在内存管理这一部分陷入混乱状态,很大一部分原因是没有弄清楚引用计数的原理,搞不明白对象的引用数量,这样就当然无法彻底释放对象的内存了,苹果官方文档在内存 ...
- swift 学习之自动引用计数
swift 学习之自动引用计数 学习和研究的主要是"实例对象和实例对象直接的相会强引用所产生的内从泄漏"和"使用闭包产生的强引用造成的内存泄漏" 注意:只有以引 ...
随机推荐
- ant编译java的例子
ant hello world 建一上文件夹HelloWorld.里面的内容如下所示: 第一个例子不讨论build1.xml和HelloWorld1.java.运行出helloworld程序要如下步骤 ...
- Codeblocks快捷键
Codeblocks快捷键: Ctrl+Shift+Enter:复制控制台exe内容 Ctrl+Z:撤销 Ctrl+F:查找 Ctrl+R:查找替换功能 Ctrl+Shift+C:加注释 C ...
- js相关小实例——div实现下拉菜单
代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w ...
- java算法 蓝桥杯(题+答案) 方格填数
6.方格填数 (结果填空) 如下的10个格子 (如果显示有问题,也可以参看[图1.jpg]) 填入0~9的数字.要求:连续的两个数字不能相邻.(左右.上下.对角都算相邻) 一共有多少种可能的填数方案 ...
- 从零开始学JavaWeb
引言 记得上学时,有位导师说过一句很经典的话:"编程语言只是工具,最重要的是掌握思想." 笔者一直主要从事.net领域的开发工作.随着工作阅历的丰富,越来越深刻的理解当年导师说 ...
- MySQL创建数据库与创建用户以及授权
1.create schema [数据库名称] default character set utf8 collate utf8_general_ci;--创建数据库 采用create schema和c ...
- OpenCV使用FindContours进行二维码定位
我使用过FindContours,而且知道有能够直接寻找联通区域的函数.但是我使用的大多只是"最大轮廓"或者"轮廓数目"这些数据.其实轮廓还有另一个很重要的性质 ...
- 使用Func<>和Action简化委托
/// <summary> /// 入口 /// </summary> public void Run() { TestDelegate t = test; t(); Acti ...
- linux性能监控分析及通过nmon_analyse生成分析报表
nmon是一款分析 AIX 和 Linux 性能的免费工具 nmon 工具还可以将相同的数据捕获到一个文本文件,便于以后对报告进行分析和绘制图形.输出文件采用电子表格的格式 (.csv). 性能介绍 ...
- 在js中,window != top 的作用
在网站的首页加上下面的javascript,就可以把自己的窗口变成是最前端的窗口.可以避免别人把你的网站放在他的iframe中,显示的就是他的网站了,误导浏览者. <script type=&q ...