C++ 简易string类实现(三)-抽离引用计数
来源:互联网 发布:虎贲计算机二级软件 编辑:程序博客网 时间:2024/06/09 01:57
在C++ 简易string类实现(二)-引用计数里我们在String类实现里加入了引用计数,从而有效地实现了字符串的共享,降低内存消耗,提升了代码的执行效率.引用计数(RC)不仅可以用于字符串,任何class如果其不同的对象可能拥有相同的值,都适用此技术.然而重写class以便运用引用计数(RC),可能是一个大工程,我们中的大部分人该做的事情还有很多.如果我们能够在一个与外界无任何关联的环境下撰写引用计数(RC)代码(并测试以及说明),然后在必要的时机把它移植到classes身上,这样可以有效地利用已有代码去实现新功能,降低时间消耗和BUG数量.
如何去撰写这样的引用计数(RC)代码?面向对象的封装和继承特性可以帮助我们有效地完成这个需求.
第一个步骤是,在C++ 简易string类实现(二)-引用计数里我们发现,StringValue内的ptr变量是和String相耦合的,但refCount和shareable和String没有必要的关联(换成其它class,对refCount和shareable的操作基本不变),因此我们将这两个变量剥离出来,以此产生一个base class RCObject,作为”reference-counted对象”之用.任何class如果希望自动拥有reference counting能力,都必须继承这个class.RCObject将”引用计数器”本身以及增减计数值的函数封装进来.此外,还包括一个函数,用来将不再被使用(也就是引用次数为0)的对象销毁掉.注:以上所需的成员函数,其实就是将C++ 简易string类实现(二)-引用计数中和refCount及shareable变量相关的操作进行的封装.
RCObject声明如下:
//class String;class RCObject{ //friend class String;public: RCObject(); /*RCObject(const RCObject& rhs_);*/ /*RCObject& operator=(const RCObject& rhs_);*/ //使RCObject成为抽象基类,但该纯虚函数需要提供 //定义,不然会使被继承的类无法在栈上创建(原因可 //查阅如何仅在堆上或栈上分配内存) virtual ~RCObject() = 0; public: void addReference(); void removeReference(); void markUnshareable(); bool isShareable() const; bool isShared() const;private: RCObject& operator=(const RCObject&) = delete;private: int refCount; bool shareable;};
RCObject定义如下:
RCObject::RCObject() :refCount(1), shareable(true){ //std::cout << "RCObject" << std::endl;}/*RCObject::RCObject(const RCObject&) :refCount(1), shareable(true){}*//*RCObject& RCObject::operator=(const RCObject&){ return *this;}*/RCObject::~RCObject(){ //std::cout << "~RCObject" << std::endl;}void RCObject::addReference(){ ++refCount;}void RCObject::removeReference(){ if (--refCount == 0) { delete this; }}void RCObject::markUnshareable(){ shareable = false;}bool RCObject::isShareable() const{ return shareable;}bool RCObject::isShared() const{ return refCount > 1;}
需要注意的地方:
(1)more effective C++中提供了RCObject(const RCObject& rhs_)拷贝构造函数,但我觉得这里暂时没有必要实现提供该函数,因为,RCObject本身是虚基类,拷贝构造只有可能由派生类的构造函数主动调用RCObject的拷贝构造函数,否则派生类的构造函数只会调用RCObject的默认(无参)构造函数;
(2)默认(无参)构造函数里,refCount应该初始化为1,more effective C++将其初始化为0,按照我的理解,是为了迎合后面实现自动操作引用次数(Reference Count)而做的铺垫,在这里,我们不引入这个技术,所以将refCount初始化为1(这里初始化为0,会造成未定义的错误);
(3)more effective C++里对RCObject的赋值运算符声明和定义如下:
RCObject& operator=(const RCObject& rhs_);RCObject& RCObject::operator=(const RCObject&){ return *this;}
其对此的解释是:”RCObeject涉及赋值动作,指向左右两方RCObject外围对象(例如本例的String对象)的个数都不会收到影响”,但如下图,
RCObject的赋值操作,必然是因为StringValue的赋值操作而引起的(RCObject是虚基类,没有实例对象,无法直接调用其赋值运算符函数),此时sv1的ptr也指向sv2的ptr所指向的字符串S2,我们知道,RCOject记录的不仅是当前有多少个String对象指向该ptr,更应该是一共有多少个String对象指向ptr所指向的字符串,如果仅仅是前者,effective C++里的写法固然没有问题,但如果是后者的话(也必须是后者),那么此时指向字符串S2的String对象就有5个,如果依旧是3个,那么当s1,s2,s3都不拥有sv1时,sv1就会将其指向的资源析构,也就是说,尽管还有sv2的ptr指向字符串S2,但字符串S2还是被释放了,这就会导致未定义行为!!!
虽然,上述的情况一般不会发生,因为在String里,StringValue对象都是heap对象,String对象的赋值操作不会导致其StringValue指针变量发生赋值操作(除非手动进行指针赋值操作),所以完全不需要担心上述问题,但该问题始终是潜在会发生的,解决的办法:A.禁止StringValue的赋值操作(将StringValue的赋值运算符设置为delete或者private);B.发生StringValue的赋值操作时,delete其ptr所指向的资源,并修改RCObject的实现,使得在发生赋值时,会正确修改refCount的值,代码相对复杂,尝试写了下很多问题,所以仅仅在这里提供一个上述的思路,若日后可以解决,再加上.这里我们采用策略A,简单而且有效.
StringValue的声明,定义如下:
struct StringValue : public RCObject { char* ptr; StringValue(const char* str_); //不可赋值操作 StringValue& operator=(const StringValue&) = delete; ~StringValue(); };String::StringValue::StringValue(const char* str_){ ptr = new char[strlen(str_) + 1]; strcpy(ptr, str_);}String::StringValue::~StringValue(){ if (ptr) { delete[] ptr; }}
全部代码如下;
声明:
class RCObject{public: RCObject(); RCObject(const RCObject& rhs_); //使RCObject成为抽象基类,但该纯虚函数需要提供 //定义,不然会使被继承的类无法在栈上创建(原因可 //查阅如何仅在堆上或栈上分配内存) virtual ~RCObject() = 0; public: void addReference(); void removeReference(); void markUnshareable(); bool isShareable() const; bool isShared() const;private: RCObject& operator=(const RCObject&) = delete;private: int refCount; bool shareable;};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_);private: struct StringValue : public RCObject { char* ptr; StringValue(const char* str_); //不可赋值操作 StringValue& operator=(const StringValue&) = delete; ~StringValue(); }; StringValue* _value;};
实现:
RCObject::RCObject() :refCount(1), shareable(true){ //std::cout << "RCObject" << std::endl;}RCObject::RCObject(const RCObject&)//调用无参构造函数,注意:该调用仅能在//初始化成员列表里,如果在函数实现内调用,//那么仅仅是在栈上生成新的对象,而不是完成//该对象的成员初始化 :RCObject(){ std::cout << "RCObject" << std::endl;}RCObject::~RCObject(){ //std::cout << "~RCObject" << std::endl;}void RCObject::addReference(){ ++refCount;}void RCObject::removeReference(){ if (--refCount == 0) { delete this; }}void RCObject::markUnshareable(){ shareable = false;}bool RCObject::isShareable() const{ return shareable;}bool RCObject::isShared() const{ return refCount > 1;}String::String(const char* str_ /* = "" */) : _value(new StringValue(str_)){}String::String(const String& str_){ if (str_._value->isShareable()) { _value = str_._value; _value->addReference(); } 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; } _value->removeReference(); if (str_._value->isShareable()) { _value = str_._value; _value->addReference(); } else { _value = new StringValue(str_._value->ptr); } return *this;}String::~String(){ _value->removeReference();}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->isShared()) { _value->removeReference(); _value = new StringValue(_value->ptr); } _value->markUnshareable(); return _value->ptr[index_];}String::StringValue::StringValue(const char* str_){ ptr = new char[strlen(str_) + 1]; strcpy(ptr, str_);}String::StringValue::~StringValue(){ if (ptr) { delete[] ptr; }}
- C++ 简易string类实现(三)-抽离引用计数
- C++ 简易string类实现(二)-引用计数
- more effective c++——Item M29 引用计数(三)带引用计数的基类的实现
- 【String】引用计数实现String
- 【C++】String类拷贝构造函数——浅拷贝优化的三种方式(引用计数)
- 【String类浅拷贝的实现】C++:String类引用计数浅拷贝的两种实现
- c++ string类实现(实现了引用计数)
- C++——std::string类的引用计数
- C++——std::string类的引用计数
- 引用计数型String类的简单实现
- C++ 简易string类实现(四)-自动操作引用次数
- String类引用计数的浅拷贝
- (String)引用计数写的拷贝
- C++ 简易string类实现(一)
- C++引用计数思想--利用引用计数器自定义String类
- 智能指针和引用计数以及String的C++实现
- String 类的编写:1.各种运算符的重载 2.用引用计数来实现String类
- objective-c 引用计数
- python3 如何转换html到pdf
- 中文分词组件
- C#自定义控件背景色透明的方法
- 视频监控中的多目标识别与跟踪技术
- Kafka,storm,Zookeeper,flume,Dubbo,Spark简介及应用
- C++ 简易string类实现(三)-抽离引用计数
- Call From SparkMaster/192.168.237.128 to 0.0.0.0:10020 failed on connection exception
- 格雷码Gray Code
- winform将图片嵌入到exe
- js 去掉字符串最后一个逗号
- [李景山php] 深入理解PHP内核[读书笔记]--第四章:函数的实现 --匿名函数及闭包
- hdu 1010 Tempter of the Bone
- 对WannaCry勒索病毒的攻击我们普通用户需要做些什么?
- c# 序列化