C++ 简易string类实现(二)-引用计数
2017-05-16 22:35
471 查看
引用计数(reference count),允许多个等值对象共享一个实值,此技术的发展有两个动机.第一,简化heap object的簿记工作,在程序执行过程中,对象的拥有权可以会转移,记录对象的拥有权不是一件简单的事情,其次,在对象没有使用者时,需要自动销毁自己,避免内存泄露;第二,如果多个对象拥有相同的值,那么将那个值存储多次往往是件愚蠢的事情,最好的做法是,让所有等值对象共享一份实值就好,这么做不仅节省内存,也使得程序速度加快,因为不再需要构造和析构同值的多余副本。
在 C++ 简易string类实现(一)中我们看到,每一个String变量,都拥有一份heap object,即使其内部完全一样,在某些情况下(仅仅对String变量读操作,而没有写),就会引发上述中的第二个问题,因此,这里通过引用计数的方式,来解决这个问题.在这里,为了节约篇幅,仅仅写出主要的几个成员函数.
类声明:
引用计数,顾名思义,是用来计数的,这里,就需要明确是对String本身计数还是对其拥有的资源进行计数,可想而知,计数的应该是后者.在类String中,字符串的值和引用次数之间有耦合(coupling)关系,利用封装的思想,将两者封装成一个类(class),不但存储引用次数,也存储他们所追踪的对象值,这个class命名为StringValue,并将其作为String的一个嵌套(nested)类.为了让所有的String成员函数(member function)都能够访问StringValue,将StringValue声明为struct,这与封装的思想似乎背道而驰,但是,将StringValue用private修饰,使仅能被String的成员函数访问,而不被任何其它人访问,这是在访问的便利性和封装之间做了一个折衷,毕竟,作为一个辅助类,其作用范围有限(String内),不希望追求极致的
4000
封装而将StringValue本身加入过多额外的成员函数.
类实现:
问题1:
在这里,仅仅重载了运算符[]的const版本,因为这份代码对读操作是正常的,但涉及写时就会出现和预期不一致的问题,因为多个String共享一个字符串,如果其中一个String变量修改字符串,其结果是,所有String的字符串都被修改了.例如,以下代码(没有提供运算符non-const重载,实际上无法通过编译,仅用于解释上述):
对str2的修改,str1内容也会发生变化.
因为C++编译期无法告诉我们operator[]是被用于读取或写,出于安全,这里假设对operator[]的调用都是写操作,以确保上述代码中的问题不会出现,non-const的operator[]代码如下:
上述对non-const的实现思路:和其它对象共享一份实值,直到我们必须对自己所拥有的那一份实值进行写动作.这个观念在计算机科学领域中有很长的历史,特别是在操作系统领域,各进程(process)之间往往允许共享某些内存分页(memory pages),直到它们打算修改属于自己的那一分页.这项技术是如此普及,因而有一个专用名称:copy-on-write(写时才复制).这是提升效率的一般化做法(也就是lazy evaluation,缓式评估)中的一剂特效药.
问题2:
如上述代码,non-const的operator[]在上述代码前,就会出现问题.
解决该问题的一个思路:为每一个StringValue对象加上一个标志(flag)变量,用于指示可否被共享.一开始,我们先竖立此标志(表示对象可被共享),但只要non-const operator[]作用域对象值身上就将标志清除.一旦标志被清除(设为false),可能永远不再改变状态.
需修改的代码如下:
上述两个问题的解决,都是以数据安全为前提,由此无法完全做到写时才复制(copy-on-write),其根本原因是,C++编译期无法告诉我们operator[]被用于读取或写(通过代理类(proxy class(代理类))可以解决这个问题).
问题3
看下述代码:
输出:
由于代码:
导致s2不可以被共享,因此复制运算符函数为:
全部代码:
注:以上内容是阅读< < more effective C++> > item 30 的总结;
在 C++ 简易string类实现(一)中我们看到,每一个String变量,都拥有一份heap object,即使其内部完全一样,在某些情况下(仅仅对String变量读操作,而没有写),就会引发上述中的第二个问题,因此,这里通过引用计数的方式,来解决这个问题.在这里,为了节约篇幅,仅仅写出主要的几个成员函数.
类声明:
class String { public: String(const char* str_ = ""); String(const String& str_); String& operator=(const String& str_); ~String(); public: const char& operator[](size_t index_) const; public: size_t getRefCount() const; private: struct StringValue { size_t refCount; char* ptr; StringValue(const char* str_); ~StringValue(); }; StringValue* _value; };
引用计数,顾名思义,是用来计数的,这里,就需要明确是对String本身计数还是对其拥有的资源进行计数,可想而知,计数的应该是后者.在类String中,字符串的值和引用次数之间有耦合(coupling)关系,利用封装的思想,将两者封装成一个类(class),不但存储引用次数,也存储他们所追踪的对象值,这个class命名为StringValue,并将其作为String的一个嵌套(nested)类.为了让所有的String成员函数(member function)都能够访问StringValue,将StringValue声明为struct,这与封装的思想似乎背道而驰,但是,将StringValue用private修饰,使仅能被String的成员函数访问,而不被任何其它人访问,这是在访问的便利性和封装之间做了一个折衷,毕竟,作为一个辅助类,其作用范围有限(String内),不希望追求极致的
4000
封装而将StringValue本身加入过多额外的成员函数.
类实现:
String::String(const char* str_ /* = "" */) : _value(new StringValue(str_)) { } String::String(const String& str_) : _value(str_._value) { ++_value->refCount; } String& String::operator=(const String& str_) { /* //这样写存在问题,例如str2 = str3; str2 = str3;即重复一次, //如果是这种写法,那么会执行后续代码一次,虽然结果是对的 //但造成了不必要的运行消耗 if (this != &str_) { return *this; }*/ if (_value == str_._value) { return *this; } std::cout << "operator=" << std::endl; if (--_value->refCount == 0) { delete _value; } _value = str_._value; ++_value->refCount; return *this; } String::~String() { if (--_value->refCount == 0) { delete _value; } } const char& String::operator[](size_t index_) const { //为了简化代码,不引入_size变量记录字符串长度 if (index_ >= strlen(_value->ptr)) { throw std::out_of_range("String out of range!"); } return _value->ptr[index_]; } size_t String::getRefCount() const { return _value->refCount; } String::StringValue::StringValue(const char* str_) : refCount(1) { ptr = new char[strlen(str_) + 1]; strcpy(ptr, str_); } String::StringValue::~StringValue() { if (ptr != nullptr) { delete[] ptr; } }
问题1:
在这里,仅仅重载了运算符[]的const版本,因为这份代码对读操作是正常的,但涉及写时就会出现和预期不一致的问题,因为多个String共享一个字符串,如果其中一个String变量修改字符串,其结果是,所有String的字符串都被修改了.例如,以下代码(没有提供运算符non-const重载,实际上无法通过编译,仅用于解释上述):
String str1 = "123"; String str2 = str1; str2[1] = 2;//没有提供运算符non-const重载,该行实际上无法通过编译
对str2的修改,str1内容也会发生变化.
String str1 = "123"; std::cout << str1[2]; //读操作 str1[1] = 2; //写操作
因为C++编译期无法告诉我们operator[]是被用于读取或写,出于安全,这里假设对operator[]的调用都是写操作,以确保上述代码中的问题不会出现,non-const的operator[]代码如下:
char& operator[](size_t index_); char& String::operator[](size_t index_) { if (index_ >= _value->refCount) { throw std::out_of_range("String out of range!"); } //本对象和其他String对象共享同一个实值 if (_value->refCount > 1) { _value->refCount--; _value = new StringValue(_value->ptr); } return _value->ptr[index_]; }
上述对non-const的实现思路:和其它对象共享一份实值,直到我们必须对自己所拥有的那一份实值进行写动作.这个观念在计算机科学领域中有很长的历史,特别是在操作系统领域,各进程(process)之间往往允许共享某些内存分页(memory pages),直到它们打算修改属于自己的那一分页.这项技术是如此普及,因而有一个专用名称:copy-on-write(写时才复制).这是提升效率的一般化做法(也就是lazy evaluation,缓式评估)中的一剂特效药.
问题2:
String s1 = "123456"; char* p = &s1[1]; String s2 = s1; *p = '8';//希望修改s1[1],结果是s1,s2均修改了
如上述代码,non-const的operator[]在上述代码前,就会出现问题.
解决该问题的一个思路:为每一个StringValue对象加上一个标志(flag)变量,用于指示可否被共享.一开始,我们先竖立此标志(表示对象可被共享),但只要non-const operator[]作用域对象值身上就将标志清除.一旦标志被清除(设为false),可能永远不再改变状态.
需修改的代码如下:
struct StringValue { size_t refCount; bool shareable; //新增此行 char* ptr; StringValue(const char* str_); ~StringValue(); }; String::StringValue::StringValue(const char* str_) : refCount(1), shareable(true) //新增此行 { ptr = new char[strlen(str_) + 1]; strcpy(ptr, str_); } String::String(const String& str_) { if (str_._value->shareable) //加入判断条件 { _value = str_._value; ++_value->refCount; } else { _value = new StringValue(str_._value->ptr); } } char& String::operator[](size_t index_) { ... _value->shareable = false; //新增此行 return _value->ptr[index_]; }
上述两个问题的解决,都是以数据安全为前提,由此无法完全做到写时才复制(copy-on-write),其根本原因是,C++编译期无法告诉我们operator[]被用于读取或写(通过代理类(proxy class(代理类))可以解决这个问题).
问题3
看下述代码:
void printRefCount(const String& s) { std::cout << s.getRefCount() << std::endl; } int main(){ { String s1 = "123456"; printRefCount(s1); String s2 = s1; char* p = &s2[1]; printRefCount(s1); printRefCount(s2); String s3; s3 = s2; printRefCount(s3); } system("pause"); return 0; }
输出:
由于代码:
char* p = &s2[1];
导致s2不可以被共享,因此复制运算符函数为:
String& String::operator=(const String& str_) { /* //这样写存在问题,例如str2 = str3; str2 = str3;即重复一次, //如果是这种写法,那么会执行后续代码一次,虽然结果是对的 //但造成了不必要的运行消耗 if (this != &str_) { return *this; }*/ if (_value == str_._value) { return *this; } std::cout << "operator=" << std::endl; if (--_value->refCount == 0) { delete _value; } if (str_.isShareable()) { _value = str_._value; ++_value->refCount; } else { _value = new StringValue(str_._value.ptr); } return *this; }
全部代码:
class String { public: String(const char* str_ = ""); String(const String& str_); String& operator=(const String& str_); ~String(); public: const char& operator[](size_t index_) const; char& operator[](size_t index_); public: size_t getRefCount() const; private: struct StringValue { size_t refCount; bool shareable; char* ptr; StringValue(const char* str_); ~StringValue(); }; StringValue* _value; };
String::String(const char* str_ /* = "" */)
: _value(new StringValue(str_))
{
}
String::String(const String& str_)
{
if (_value->shareable)
{
_value = str_._value;
++_value->refCount;
}
else
{
_value = new StringValue(str_._value->ptr);
}
}
String& String::operator=(const String& str_) { /* //这样写存在问题,例如str2 = str3; str2 = str3;即重复一次, //如果是这种写法,那么会执行后续代码一次,虽然结果是对的 //但造成了不必要的运行消耗 if (this != &str_) { return *this; }*/ if (_value == str_._value) { return *this; } std::cout << "operator=" << std::endl; if (--_value->refCount == 0) { delete _value; } if (str_.isShareable()) { _value = str_._value; ++_value->refCount; } else { _value = new StringValue(str_._value.ptr); } return *this; }
String::~String()
{
if (--_value->refCount == 0)
{
delete _value;
}
}
const char& String::operator[](size_t index_) const
{
//为了简化代码,不引入_size变量记录字符串长度
if (index_ >= strlen(_value->ptr))
{
throw std::out_of_range("String out of range!");
}
return _value->ptr[index_];
}
char& String::operator[](size_t index_)
{
if (index_ >= strlen(_value->ptr))
{
throw std::out_of_range("String out of range!");
}
//本对象和其他String对象共享同一个实值
if (_value->refCount > 1)
{
_value->refCount--;
_value = new StringValue(_value->ptr);
}
_value->shareable = false;
return _value->ptr[index_];
}
size_t String::getRefCount() const
{
return _value->refCount;
}
String::StringValue::StringValue(const char* str_)
: refCount(1),
shareable(true)
{
ptr = new char[strlen(str_) + 1];
strcpy(ptr, str_);
}
String::StringValue::~StringValue()
{
if (ptr != nullptr)
{
delete[] ptr;
}
}
注:以上内容是阅读< < more effective C++> > item 30 的总结;
相关文章推荐
- C++ 简易string类实现(三)-抽离引用计数
- 【String类浅拷贝的实现】C++:String类引用计数浅拷贝的两种实现
- C++ 简易string类实现(四)-自动操作引用次数
- 【String类浅拷贝的实现】C++:String类引用计数浅拷贝的两种实现
- C++——std::string类的引用计数
- C++ 引用计数技术及智能指针的简单实现及改进
- 【转】C++ 引用计数技术及智能指针的简单实现
- C++简单实现对象引用计数示例
- C++引用计数思想--利用引用计数器自定义String类
- 智能指针和引用计数以及String的C++实现
- C++ 引用计数技术及智能指针的简单实现
- 非常经典的C++ 引用计数技术及智能指针的简单实现
- share_ptr 实现c++ 句柄引用计数
- C++ 简易string类实现(一)
- C++ 简易string类实现(六)-真正的写时复制
- C++ 引用计数技术及智能指针的简单实现
- 【C++】智能指针之引用计数的实现
- c++引用计数实现
- c++引用计数实现
- more effective c++——Item M29 引用计数(三)带引用计数的基类的实现