使用引用计数和copy-on_write实现String类
Posted hhwyt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用引用计数和copy-on_write实现String类相关的知识,希望对你有一定的参考价值。
本文写于2017-01-18,从老账号迁移到本账号,原文地址:https://www.cnblogs.com/huangweiyang/p/6295420.html
这算是我开始复习的内容吧,关于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_ = ‘ ‘;
}
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_ = ‘ ‘;
}
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!=‘ ‘; ++s)
*s -= 32;
}
private:
string_rep *rep;
};
解决了资源的销毁问题,那么还有一个新的问题:多个对象共享一个字符串资源,那么如果某个对象需要修改字符串,怎么处理?答案:copy-on-write技术。
copy-on-write技术,简称COW技术。意思就是只有在写资源的时候才拷贝,读资源并不拷贝。当我们需要修改字符串,这就是写了。比如上面的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类的主要内容,如果未能解决你的问题,请参考以下文章