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 类的实现(2)引用计数与写时拷贝的更多相关文章

  1. String类的实现(4)写时拷贝浅析

    由于释放内存空间,开辟内存空间时花费时间,因此,在我们在不需要写,只是读的时候就可以不用新开辟内存空间,就用浅拷贝的方式创建对象,当我们需要写的时候才去新开辟内存空间.这种方法就是写时拷贝.这也是一种 ...

  2. 标准C++类std::string的内存共享和Copy-On-Write(写时拷贝)

    标准C++类std::string的内存共享,值得体会: 详见大牛:https://www.douban.com/group/topic/19621165/ 顾名思义,内存共享,就是两个乃至更多的对象 ...

  3. 深拷贝&浅拷贝&引用计数&写时拷贝

    (1).浅拷贝: class String { public: String(const char* str="") :_str(]) { strcpy(_str,str); } ...

  4. String写时拷贝实现

    头文件部分 1 /* 版权信息:狼 文件名称:String.h 文件标识: 摘 要:对于上版本简易的String进行优化跟进. 改进 1.(将小块内存问题与大块分别对待)小内存块每个对象都有,当内存需 ...

  5. 转C++之stl::string写时拷贝导致的问题

    前几天在开发某些数据结构到文件的 Dump 和 Load 功能的时候, 遇到的一个 bug . [问题复现] 问题主要出在 Load 过程中,从文件读取数据的时候, 直接使用 fread 的去操作 s ...

  6. String类的写时拷贝

    #include<iostream>using namespace std; class String;ostream& operator<<(ostream & ...

  7. (转)C++——std::string类的引用计数

    1.概念 Scott Meyers在<More Effective C++>中举了个例子,不知你是否还记得?在你还在上学的时候,你的父母要你不要看电视,而去复习功课,于是你把自己关在房间里 ...

  8. php 垃圾回收机制----写时复制和引用计数

    PHP使用引用计数和写时复制来管理内存.写时复制保证了变量间复制值不浪费内存,引用计数保证了当变量不再需要时,将内存释放给操作系统. 要理解PHP内存管理,首先要理解一个概念----符号表. 符号表的 ...

  9. String 类的实现(3)引用计数实现String类

    我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N的值主要用于析 ...

随机推荐

  1. 数据库无法启动ORA-01034: ORACLE not available

    错误场景: 1.数据库未启动,查询v$instance报错 SQL> select status from v$instance; select status from v$instance * ...

  2. Java HashMap工作原理及实现[转]

    原文:http://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE ...

  3. EM算法(坐标上升算法)

    原文地址:https://www.cnblogs.com/to-creat/p/6075322.html 机器学习十大算法之一:EM算法.能评得上十大之一,让人听起来觉得挺NB的.什么是NB啊,我们一 ...

  4. C++11 override 和 final 关键字

    C++11之前,一直没有继承控制关键字.禁用一个类的进一步衍生是可能的但也很棘手.为避免用户在派生类中重载一个虚函数,你不得不向后考虑. C++ 11添加了两个继承控制关键字:override和fin ...

  5. Python os.walk文件遍历用法【转】

    python中os.walk是一个简单易用的文件.目录遍历器,可以帮助我们高效的处理文件.目录方面的事情. 1.载入 要使用os.walk,首先要载入该函数 可以使用以下两种方法 import os ...

  6. HTTP请求头信息

    常用请求头 User-Agent : 浏览器信息Host : 服务区域名Referer : 通过哪里的链接过来的Origin : 跨域相关Content-Type : POST和PUT请求的数据类型C ...

  7. Qt5.10.1 在windows下vs2017静态编译

    1.在计算机上安装python库和perl库(因为后续的静态编译需要用到这两种语言),可以在命令行敲击“python”和“perl -v”检查是否安装成功. 2.修改msvc-desktop.conf ...

  8. web@HTML常用标签分类,标签嵌套规则

    一.html标签又叫做html元素,它分为块级元素和内联元素(也可以叫做行内元素),都是html规范中的概念.1.块级元素块级元素是指本身属性为display:block;的元素.因为它自身的特点,我 ...

  9. Light OJ 1095

    题意: 给你 N 个数, 总共有 N! 种排列, 现在 要你统计前 M 个数 刚好 有K 个数 在原来的位置上 的排列个数 思路: 首先 M 中选 K C(m,k): 则 共 剩下 n - k 个数, ...

  10. Tomcat 部署项目的三种方法(转)

    转自:https://www.cnblogs.com/ysocean/p/6893446.html#_label0 1.下载 Tomcat 服务器 ①.官网下载地址:http://tomcat.apa ...