more effective c++——Item M29 引用计数(一)简略的rfstring类设计和写时拷贝
2017-05-03 00:50
721 查看
引用计数的特点及作用
1. 引用计数允许多个相同值得对象共享这个值得实现
2. 引用计数可以节省空间、拷贝及析构的开销
3. 引用计数可以简化对象的追踪过程,比如在垃圾体系回收中
引用计数的简单实现:
如果要设计一个类rfstring作为string的引用计数版,则rfstring至少要包含2个成员变量:char *str,int rfcount。因此可以设计为以下形式:
以上设计存在几个问题:
1. 由于需要对char 数据进行引用计数,如果rfstring类直接持有为char 分配的堆内存,则相同的字符串仍然创建多个实例,因此需要考虑将字符串放到另外一个类中,然后让rfstring间接持有char *——假定该类名称为stringvalue
2. 如果rfstring直接持有stringvalue的对象,则仍然会创建多个实例,因此需要持有其指针
3. 由于stringvalue不需要对外部暴露,因此将stringvalue声明为rfstring类的私有类
4. 由于rfstring类含有堆内存,因此需要提供自己的拷贝构造与赋值操作符。
因此我们的rfstring类设计如下:
上述调用的过程中存在2个问题
在第四点中,会构造一个rfstring临时对象,此时引用计数为1,然后调用rfstring &operator=(const char *str)将引用计数自增到2,临时变量析构后又变为1,但是由于临时变量的原因会影响效率,需要提供rfstring &operator=(const char *str)
在第四点中,相同的字符串又在堆上重新分配了内存,不是实际意义的引用计数,这个问题可以后续通过2个方式解决
为rfstring添加operator=(const char *)
此rfstring类不含油缺省构造函数,可以进行添加,使strvalue为null,然后调用的过程中,检查strvalue是否为空即可。
上述rfstring类中没有提供std::string类中的operator[]操作,这个操作是必须的,但是它会带来问题,因为你不知道用户调用该操作时,是否会改变该下标对应字符的值,如下:
对于一个非const的rfstring对象,我们应该允许用户修改其某个下标的值,因此第二行代码是合法的,但是此时rf2实际指向的内容不再是”more effective c++”。如何避免呢?当用户试图修改非const的rfstring对象的下标值时重新拷贝一份char *的值即可。那如何知道用户是否要修改其值呢?可以假定如下:
假定用户调用所有的非const的rfstring对象operator[]时都会修改其值
用户调用的const的rfstring对象的operator[]都返回const char &,符合常理
实现代码如下:
由于提供了operator[]操作,因此外部可以得到某个下标对应字符的地址,如果这个地址被外部拿到,将造成rfstring对象的值被修改而不知的问题,如下:
该如何解上述问题?stanly lippman提供了三个建议:
1. 忽略它
2. 写入文档
3. 解决它——为rfstring类提供一个是否可以共享的标志,但仍然丑陋,代码如下
然后在相应的函数中添加对shareable的操作即可
完整的代码如下:
1. 引用计数允许多个相同值得对象共享这个值得实现
2. 引用计数可以节省空间、拷贝及析构的开销
3. 引用计数可以简化对象的追踪过程,比如在垃圾体系回收中
引用计数的简单实现:
如果要设计一个类rfstring作为string的引用计数版,则rfstring至少要包含2个成员变量:char *str,int rfcount。因此可以设计为以下形式:
class rfstring { public: rfstring(const char * value); ~rfstring(); rfstring(const rfstring & rhl); private: char *str; int count; }; rfstring::rfstring(const char * value):count(1) { str = new char[strlen(value) + 1]; strcpy(str, value); } rfstring::~rfstring() { //考虑该在什么条件下删除堆内存? }
以上设计存在几个问题:
1. 由于需要对char 数据进行引用计数,如果rfstring类直接持有为char 分配的堆内存,则相同的字符串仍然创建多个实例,因此需要考虑将字符串放到另外一个类中,然后让rfstring间接持有char *——假定该类名称为stringvalue
2. 如果rfstring直接持有stringvalue的对象,则仍然会创建多个实例,因此需要持有其指针
3. 由于stringvalue不需要对外部暴露,因此将stringvalue声明为rfstring类的私有类
4. 由于rfstring类含有堆内存,因此需要提供自己的拷贝构造与赋值操作符。
因此我们的rfstring类设计如下:
// rfstring.h #pragma once class rfstring { public: rfstring(const char * value); ~rfstring(); rfstring(const rfstring & rhl); //rfstring & operator=(const char *str);// 可以不实现,如果不实现,通过一个char *对rfstring对象赋值时,编译器会将char *转换为rfstring对象后,调用operator(const rfstring &) rfstring &operator=(const rfstring &value); private: class stringvalue { public: char *value; int count; stringvalue(const char *str); ~stringvalue(); }; stringvalue *strvalue; };
// rfstring.cpp "rfstring.h" #include <cstring> rfstring::rfstring(const char * value) { strvalue = new stringvalue(value); } rfstring::rfstring(const rfstring & rhl) { strvalue = rhl.strvalue; strvalue->count++; // 由于rhl是const,因此只能通过this指针的strvalue对count进行自增 } // 由于含有堆内存,因此需要考虑深拷贝和浅拷贝的问题 // 需要防止自赋值 // 如果引用计数为1,则需要将原始的内存释放掉 rfstring & rfstring::operator=(const rfstring &rhl) { if (this == &rhl) { return *this; } // 判断是否需要释放内存 if (--strvalue->count == 0) { delete strvalue; } strvalue = rhl.strvalue; strvalue->count++; return *this; } rfstring::~rfstring() { if (--strvalue->count == 0) { delete strvalue; } strvalue = NULL; } rfstring::stringvalue::stringvalue(const char *str):count(1) { value = new char[strlen(str) + 1]; strcpy(value, str); } rfstring::stringvalue::~stringvalue() { delete value; value = NULL; } lude "rfstring.h" void test() { rfstring rf1 = "more effective c++"; // 1.调用rfstring(const char * value); rfstring rf2 = rf1; // 2.调用rfstring(const rfstring & rhl); rfcount++ rfstring rf3("effective c++"); // 3.调用rfstring(const char * value); rf3 = "more effective c++"; // 4.先调用rfstring(const char * value)构造一个临时的rfstring对象,然后再调用rfstring &operator=(const rfstring &value),然后析构掉临时的对象 } int main() { test(); return 0; }
上述调用的过程中存在2个问题
在第四点中,会构造一个rfstring临时对象,此时引用计数为1,然后调用rfstring &operator=(const char *str)将引用计数自增到2,临时变量析构后又变为1,但是由于临时变量的原因会影响效率,需要提供rfstring &operator=(const char *str)
在第四点中,相同的字符串又在堆上重新分配了内存,不是实际意义的引用计数,这个问题可以后续通过2个方式解决
为rfstring添加operator=(const char *)
// 用来解决效率问题 rfstring & operator=(const char *str); rfstring & rfstring::operator=(const char * str) { if (--strvalue->count == 0) { delete str 4000 value; } strvalue = new stringvalue(str); return *this; }
此rfstring类不含油缺省构造函数,可以进行添加,使strvalue为null,然后调用的过程中,检查strvalue是否为空即可。
上述rfstring类中没有提供std::string类中的operator[]操作,这个操作是必须的,但是它会带来问题,因为你不知道用户调用该操作时,是否会改变该下标对应字符的值,如下:
rfstring rf1 = "more effective c++"; // 1.调用rfstring(const char * value); rfstring rf2(rf1); rf1[10] = 'Z';
对于一个非const的rfstring对象,我们应该允许用户修改其某个下标的值,因此第二行代码是合法的,但是此时rf2实际指向的内容不再是”more effective c++”。如何避免呢?当用户试图修改非const的rfstring对象的下标值时重新拷贝一份char *的值即可。那如何知道用户是否要修改其值呢?可以假定如下:
假定用户调用所有的非const的rfstring对象operator[]时都会修改其值
用户调用的const的rfstring对象的operator[]都返回const char &,符合常理
实现代码如下:
const char &operator[](const int index) const; char &operator[](const int index); const char & rfstring::operator[](const int index) const { return strvalue->value[index]; } // 非const版本,需要进行拷贝 char & rfstring::operator[](const int index) { if (strvalue->count > 1) { strvalue->count--; strvalue = new stringvalue(strvalue->value); } return strvalue->value[index]; }
由于提供了operator[]操作,因此外部可以得到某个下标对应字符的地址,如果这个地址被外部拿到,将造成rfstring对象的值被修改而不知的问题,如下:
String s1 = "Hello"; char *p = &s1[1]; *p = 'x';
该如何解上述问题?stanly lippman提供了三个建议:
1. 忽略它
2. 写入文档
3. 解决它——为rfstring类提供一个是否可以共享的标志,但仍然丑陋,代码如下
struct StringValue { int refCount; bool shareable; // add this char *data; StringValue(const char *initValue); ~StringValue(); };
然后在相应的函数中添加对shareable的操作即可
完整的代码如下:
// rfstring.h #pragma once class rfstring { public: rfstring(const char * value); ~rfstring(); const char &operator[](const int index) const; char &operator[](const int index); rfstring(const rfstring & rhl); rfstring & operator=(const char *str);// 可以不实现,如果不实现,通过一个char *对rfstring对象赋值时,编译器会将char *转换为rfstring对象后,调用operator(const rfstring &) rfstring &operator=(const rfstring &value); private: class stringvalue { public: char *value; int count; bool shareable; // add this stringvalue(const char *str); ~stringvalue(); }; stringvalue *strvalue; };
// rfstring.cpp #include "rfstring.h" #include <cstring> rfstring::rfstring(const char * value) { strvalue = new stringvalue(value); } rfstring::rfstring(const rfstring & rhl) { if (rhl.strvalue->shareable) { strvalue = rhl.strvalue; strvalue->count++; // 由于rhl是const,因此只能通过this指针的strvalue对count进行自增 } else { strvalue = new stringvalue(rhl.strvalue->value); } } rfstring & rfstring::operator=(const char * str) { if (--strvalue->count == 0) { delete strvalue; } strvalue = new stringvalue(str); return *this; } // 由于含有堆内存,因此需要考虑深拷贝和浅拷贝的问题 // 需要防止自赋值 // 如果引用计数为1,则需要将原始的内存释放掉 rfstring & rfstring::operator=(const rfstring &rhl) { if (this == &rhl) { return *this; } // 判断是否需要释放内存 if (--strvalue->count == 0) { delete strvalue; } strvalue = rhl.strvalue; strvalue->count++; return *this; } rfstring::~rfstring() { if (--strvalue->count == 0) { delete strvalue; } strvalue = NULL; } const char & rfstring::operator[](const int index) const { return strvalue->value[index]; } // 非const版本,需要进行拷贝 char & rfstring::operator[](const int index) { if (strvalue->count > 1) { strvalue->count--; strvalue = new stringvalue(strvalue->value); } strvalue->shareable = false; return strvalue->value[index]; } rfstring::stringvalue::stringvalue(const char *str):count(1),shareable(true) { value = new char[strlen(str) + 1]; strcpy(value, str); } rfstring::stringvalue::~stringvalue() { delete value; value = NULL; }
相关文章推荐
- 【转载】引用计数(More Effective c++ Item M29)
- more effective c++——Item M29 引用计数(二)带静态成员变量的rfstring类实现
- more effective c++——Item M29 引用计数(三)带引用计数的基类的实现
- read Item 25 of《More Effective C++》
- More Effective C++ 读书摘要(五、技巧2)Item28 - 29
- (大卫的阅读笔记)More Effective C++ Item 附2:一个auto_ptr的实现实例
- More Effective C++:Item 27
- More Effective C++ Item14:明智运用exception specifications
- 读《More Effective C++35个改善编程与设计的有效方法》之条款3:绝对不要以多态方式处理数组
- 《More Effective C++ 35个改善编程与设计的有效方法》——第二章笔记
- 《More Effective C++ 35个改善编程与设计的有效方法》——第一章笔记
- 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎
- Item 2:Prefer C++-style casts.(More Effective C++)
- More Effective C++ 读书摘要(六、杂项)Item32 - 35
- More Effective C++:Item 27
- More Effective C++ 读书摘要(五、技巧1)Item25 - 27
- more effective c++笔记1-----Item M2:尽 量使用C++风格的类型转换
- 《more effective c++》Item M1:指针与引用的区别
- 《More Effective C++》 Item M1:指针与引用的区别
- 读书笔记 effective c++ Item 19 像设计类型(type)一样设计类