这算是我开始复习的内容吧,关于 String 类半年前写过,最近拿出来溜溜,免得面试被问到而自己又忘记了。

之前的文章地址:[C++ 引用计数思想--利用引用计数器自定义 String 类](C++ 引用计数思想--利用引用计数器自定义 String 类)。

首先上一个 String 类最简明的写法,没有用到引用计数和 COW,不过写法实在是很简单,不容易出错。先看代码,然后说弊端。

#include <iostream>
#include <string.h> class my_string {
public:
my_string(const char* str = NULL) {
if(str == NULL){
str_ = new char[1];
*str_ = '\0';
}
else{
str_ = new char[strlen(str)+1];
strcpy(str_, str);
}
}
my_string(const my_string& other)
: str_(new char[other.size()]+1) { //直接使用参数列表
strcpy(str_, other.c_str());
}
my_string& operator=(my_string other) { //按值传递
swap(other);
return *this;
}
~my_string() {
//delete []str_;
str_ = NULL;
}
public:
size_t size() const {
return strlen(str_);
}
const char* c_str() const {
return str_;
}
void swap(my_string& other) {
std::swap(str_, other.str_);
}
public:
void show() const {
std::cout<<str_<<std::endl;
}
private:
char* str_;
};

上述就是最简洁方案的代码。不过,这种方案有一个弊端,甚至是错误。那就是我们不能在析构函数中直接 delete []str_ 。因为上述方案,可能造成两个对象对字符串内存资源的共享。如果析构函数中直接 delete 掉内存,那么两个对象,意味着该内存要被 delete 两次。其结果可想而知。

为了解决这个问题,我们引入了引用计数思想。使用引用计数,对拥有字符串资源的对象数目进行计数。只有当拥有该字符串资源的对象数目为 0 时,才销毁字符串内存资源。这就能够保证该内存只被 delete 一次。

#include <string.h>
#include <iostream> class string_rep {
friend class my_string;
public:
string_rep(const char* str = NULL) : use_count_(1) {
if(str == NULL){
str_ = new char[1];
*str_ = '\0';
}
else{
str_ = new char [strlen(str)+1];
strcpy(str_, str);
}
} //trivial copy assignment ~string_rep() {
delete []str_;
str_ = NULL;
}
public:
unsigned int use_count() const {
return use_count_;
}
const char* c_str() const {
return str_;
}
private:
void increment() {
++use_count_;
}
void decrement() {
if(--use_count_ == 0)
delete this;
}
private:
char *str_;
unsigned int use_count_;
}; class my_string {
public:
my_string(const char* str = NULL)
: rep(new string_rep(str)) {
}
my_string(const my_string& other) {
rep = other.rep;
rep->increment();
}
my_string& operator=(const my_string& other) {
if(this != &other){
rep->decrement(); //先减一
rep = other.rep;
rep->increment();
}
return *this;
}
~my_string() {
rep->decrement(); //不要忘记这步
}
public:
unsigned int use_count() const {
return rep->use_count();
}
const char* c_str() const {
return rep->c_str();
}
void tupper() {
if(use_count() > 1){
string_rep* new_rep = new string_rep(rep->str_);
rep->decrement();
rep = new_rep; //替换操作
} for(char *s=rep->str_; *s!='\0'; ++s)
*s -= 32;
}
private:
string_rep *rep;
};

解决了资源的销毁问题,那么还有一个新的问题:多个对象共享一个字符串资源,那么如果某个对象需要修改字符串,怎么处理?答案:COW 技术。

COW(copy on write) 技术,就是只有在写资源的时候才拷贝,读资源并不拷贝。修改字符串就属于写资源。例如上面的 tupper() 函数,这就是 copy-on-write 技术在string 类的一个简单实现。修改字符串时,我们直接 new 出一个新的 string_rep 类替换旧的,在新的 string_rep 上操作即可。不过要注意,要保证旧有资源的释放,否则会造成内存泄漏问题。

在上面的实现中,还有一个技巧,那就是 delete this。这可是一个争议的东西,不过在引用计数中,使用 delete this 是安全的的。因为引用计数中,就本例来说,string_rep 类仅暴露给了 my_string 类,而 delete_this 又是引用计数为 0 导致的,要么是在 my_string 类的析构函数中引起,要么是在 copy-on-write 函数中引起,这都是安全的。this 指针在 delete 之后并不会暴露给外部。

使用 delete this 的注意事项:

1.this 对象必须是用 new 操作符分配的(而不是 new[],也不是 placement new)。

2.dlete this 后,不能访问该对象的任何成员变量及虚函数。因为 delete this 会销毁成员原谅以及 vptr。但是注意,并不销毁 vtbl,因为 vtbl 是该类所有对象共有的,如果你知道 C++ 对象模型这就很好理解了,这里不赘述。

3.delete this 后,不能再访问 this 指针。

好了,以上这些就是关于string类的一些技巧,不过实际上有很优秀的编程思想蕴含在里面,慢慢提高吧。

使用引用计数和copy-on_write实现String类的更多相关文章

  1. Objective-C内存管理之-引用计数

    本文会继续深入学习OC内存管理,内容主要参考iOS高级编程,Objective-C基础教程,疯狂iOS讲义,是我学习内存管理的笔记 内存管理 1 内存管理的基本概念 1.1 Objective-C中的 ...

  2. 【转载】C++应用引用计数技术

    原帖:http://www.cnblogs.com/chain2012/archive/2010/11/12/1875578.html 因为Windows的内核对象也运用了引用计数,所以稍作了解并非无 ...

  3. com关于引用计数

    实现引用计数并不难,但在什么层次上进行引用计数呢? 依照com规范,一个com组件能够实现多个com对象.而且每一个com对象又能够支持多个com接口,这样的层次结构为我们实现引用计数提供了多种选择方 ...

  4. 【python测试开发栈】python内存管理机制(一)—引用计数

    什么是内存 在开始进入正题之前,我们先来回忆下,计算机基础原理的知识,为什么需要内存.我们都知道计算机的CPU相当于人类的大脑,其运算速度非常的快,而我们平时写的数据,比如:文档.代码等都是存储在磁盘 ...

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

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

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

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

  7. String 类的实现(2)引用计数与写时拷贝

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

  8. 从sizeof(string)到引用计数的漫游

    前言: 说是漫游,其实就是扯,一点一点的扯. 话说之前参加华为的德州扑克比赛,我用C++解析消息的时候碰到一个小问题,就是定长收消息的时候出错,在Linux下调了很久很久,终于发现,sizeof(st ...

  9. 【M29】引用计数

    1.引用计数这项技术,是为了让等值对象对象共享同一实体.此技术的发展有两个动机:a.记录堆上分配的对象,是垃圾回收机制的简单原理:b.节省内存,多个对象具有相同的值,存储多次很笨.速度更快,等值对象避 ...

随机推荐

  1. asp.net 根据连接地址保存文件,图片

    第一种方式  下载图片,文件 WebClient my = new WebClient(); string url = "http://localhost:1015/resource/loa ...

  2. Autowired(required=true)

    问题原因 没有实现类的接口上添加了@Autowired注解 问题解决 删掉@Autowired注解 bug详情 Description: Field userDAO in com.crab.servi ...

  3. VBoxManage

    虚拟机名称 centos6.9-1 centos6.9-2 centos6.9-3 使用命令开机 虚拟机 VBoxManage startvm 'centos6.9-1' 正常启动 VBoxManag ...

  4. kali linux 使用笔记本快捷键调节音量

    环境:kali 2018.3a(xface桌面版),自带PulseAudio控制音量. 以前在windows时笔记本是Fn+F1这些来调节音量的,装了kali后原来调节亮度.触控板的键还能用,唯独音量 ...

  5. selenium+java 数据驱动

    一.数据驱动测试概念 数据驱动测试是相同的测试脚本使用不同的测试数据执行,测试数据和测试行为完全分离. 二.实施数据驱动测试的步骤: 1.编写测试脚本,脚本需要支持程序对象.文件或者数据库读入测试数据 ...

  6. setTimeout异步

    同步任务和异步任务 同步和异步操作的区别就是是否阻碍后续代码的执行. 同步任务是那些没有被引擎挂起.在主线程上排队执行的任务.只有前一个任务执行完毕,才能执行后一个任务. 异步任务是那些被引擎放在一边 ...

  7. 杂记-python

    1.在cmd输入python -V显示当前python的版本信息,一定是大写的V 2.输入python,进入python解释器里面 3.在解释器中,输入exit()或者quit()会退出,一定要加括号 ...

  8. Mysql 数据库日志与数据文件分开

    参考:https://blog.csdn.net/jiao_fuyou/article/details/78366621 myql默认配置情况下,二进制日志文件是保存在默认的数据目录 data 下,如 ...

  9. charles\mitmproxy\appium的安装与使用

    一.charles安装与激活1.https://www.charlesproxy.com/documentation/installation/下载dmg包安装后要将应用添加到Mac的应用目录中,一般 ...

  10. 8.4 GOF设计模式三: 外观模式 Facade

    GOF设计模式三: 外观模式 Facade  “现有系统”功能强大.复杂,开发“新系统”需要用到其中一部分,但又要增加一部 分新功能,该怎么办?4.1 Facade Pattern: Key Fea ...