在C++ 简易string类实现(二)-引用计数中,我们引入了写时复制(copy on write),但因为C++编译期无法告诉我们operator[]是被用于读取或写,出于安全,这里假设对non-const 的operator[]的调用都是写操作,这样虽然能够正常运行,但是诸如下面的代码:
String str1 = "123";
std::cout << str1[2]; //读操作
str1[1] = 2; //写操作
第2行里,明显是一个读操作,但是为了安全,也会被假设在写操作.这样使用上固然没问题,但是依然不是真正的写时复制(copy on write),那么有没有办法使得编译期针对operator[],可以”智能”地知道,何时是写操作,何时是读操作吗?
下面是C++ 简易string类实现(二)-引用计数中String类的non-const版的operator[]:
char& String::operator[](size_t index_)
返回类型是char&,这里的问题是,对于写操作或者读操作,char&没有有效的办法做区分.在 C++ 简易string类实现(五)-进一步抽象中,我们引入CountHolder的一个思想是,”加上一层间接性”.这里我们同样加入一层间接性,将返回的char类型包装是一个类,我们知道,对于一个类(而不是其成员函数的返回类型),可以很容易知道其调用是写操作还是读操作(利用所调用的成员函数区分).基于此,我们引入proxy class(代理类),令String的operator返回是字符串中字符的proxy,而不返回字符本身.然后我们可以等待,看看这个proxy如何被调用.如果它被读,我们可以(有点过时地)将operator[]的调用动作视为一个读取动作.如果它被写,我们必须将operator[]的调用视为一个写操作.
对于即将使用的proxy,有3件事需呀做:
(1)产生它,本例也就是指定它代表哪一个字符串中的哪一个字符;
(2)以它作为赋值动作(assignment)的目标(接受端),这种情况下,是对它所代表的字符串内的字符做赋值动作.如果这么使用,proxy代表的将是”调用operator[]函数”的那个字符串的左值运用.
(3)以其它方式使用之.如果这么使用,proxy表现的是”调用operator[]函数”的那个字符串的右值运用.
下面是一个带有引用计数特性的String class,其中利用proxy class 来区分operator[]的左值运用(写操作)和右值运用(读操作):
class String
{
friend class CharProxy; //访问String私有变量
public:
String(const char* str_ = "");
class CharProxy
{
public:
CharProxy(String& str_, int index_); //构造
CharProxy& operator=(const CharProxy& rhs_); //左值运用
CharProxy& operator=(char ch_); //左值运用
operator char() const; //右值运用
private:
//记录所引用的String对象和当前字符的下标,
//在左值运用时,可以修改String对象的值
String& _theString;
int _charIndex;
};
const CharProxy operator[](int index) const;
CharProxy operator[](int index);
private:
struct StringValue : public RCObject
{
char* ptr;
StringValue(const char* str_);
StringValue(const StringValue& rhs_);
~StringValue();
private:
void init(const char* str_);
};
RCPtr _value;
};
这里的String class和C++ 简易string类实现(四)-自动操作引用次数中的那个String class之间唯一的差别就是,此处的两个operator[]都返回CharProxy对象.然而对于用户可以忽略这点(对于某些操作,例如对CharProxy变量做&操作会有问题,下面会说到),犹如operator[]返回字符一样:
String str1 = "123";
std::cout << str1[2]; //合法,有效运转
str1[1] = 2; //合法,有效运转
考虑这行代码
std::cout << str1[2];
表达式str1[2]产生一个CharProxy对象.此对象不曾定义output操作符,所以编译器赶紧寻找一个它能够实施的隐式类型转换,使得operator<<调用操作能够成功.编译器真的找到了一个:将CharProxy隐式转型为char.此转换函数声明于CharProxy函数内.于是编译器自动调用这个转换操作符,于是CharProxy所表现的字符串字符被打印出去.这是典型的CharProxy-to-char转换,发生在所有”被用来作为右值”的CharProxy对象身上.
左值运用的处理方式又不相同,例如这行代码
str1[1] = 2;
和上例一样,表达式str1[1]导出一个CharProxy对象,但这次这个对象是assignment动作的目标物.会调用哪个assignment操作符呢?由于目标物是个CharProxy,所以被调用的assignment操作符会是CharProxy class所定义的那个.这很重要,因为在CharProxy的assignment操作符内,我们确知”被赋值的CharProxy对象被用来作为一个左值”.我们因此知道proxy所代表的那个”字符串内的字符”将被用来作为一个左值,并因此采取所有必要行动,对该字符实施左值处理.
即使是以下这段代码:
String s1 = "1234";
String s2 = "12333";
s1[3] = s2[3];
最后一行调用的两个CharProxy对象的assignment操作符,在该操作符中我们知道左端对象被用来作为一个左值,右端对象被用来作为一个右值.
对于String的operator[]
const CharProxy operator[](int index) const;
CharProxy operator[](int index);
每个函数都只是产生并返回”被请求字符”的一个proxy.没有任何动作施加于此字符身上:我们延缓此等行为,直到知道该行为是”读取”还是”写”.
虽然Proxy class很适合用来区分operator[]的左值运用和右值运用,但是这项技术并非没有缺点.我们希望proxy object能够无间隙地取代它们所代表的对象,但是这样的想法却很难达成.因为除了赋值(assignment)之外,对象亦有可能在其它情境下被当做左值使用,而在那种情况下的proxies常常会有与真实对象不同的行为.
例如下述代码:
String s1 = "123456";
char* p = &s2[1]; //错误,无法通过编译
第二行代码无法通过编译的原因是,&s2[1]返回的是一个CharProxy*类型指针,由于不存在任何函数可以将CharProxy * 转换为 char *,所以上述的p的初始化动作无法通过编译.一般而言,”对proxy取址”所获得的指针类型和”对真实对象地址”所获得的指针类型不同.
为了消除这个难点,我们需要在CharProxy class内将取址(address of)操作符加以重载:
class String
{
public:
...
class CharProxy
{
public:
...
char* operator&();
const char* operator&() const;
...
};
...
};
对应的实现为:
char* String::CharProxy::operator&()
{
//确定"标记的字符"(本函数返回一个指针指向它)所属的字符串实值不为
//任何其它String对象共享(如果共享,就做一份专属副本出来)
if (_theString._value->isShared())
{
_theString._value = new StringValue(_theString._value->ptr);
}
//我们不知道clients会将本函数返回的指针保留多久,所以"目标字符"
//所属的字符串实值(一个StringValue对象)绝不可以被共享
_theString._value->markUnshareable();
return &(_theString._value->ptr[_charIndex]);
}
const char* String::CharProxy::operator&() const
{
return &(_theString._value->ptr[_charIndex]);
}
此时,
String s1 = "123456";
char* p = &s2[1]; //编译通过
问题得以解决,但本质上,CharProxy和char是不同的,我们可以通过重载的方式解决遇到的问题,这样无疑增加了工作量,但现在我们在copy-on-copy上的工作已经做到很好了,这些工作量的增加还是可以接受的,当然可以通过将一些通用的运算符加以封装,得以复用,然后针对具体的类,再增加额外所需的函数.
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;
};
template
class RCPtr
{
public:
RCPtr(T* realPtr = 0);
RCPtr(const RCPtr& rhs_);
RCPtr& operator=(const RCPtr& rhs_);
~RCPtr();
public:
T* operator->() const;
T& operator*() const;
private:
void init();
private:
T* _ptr;
};
class String
{
friend class CharProxy;
public:
String(const char* str_ = "");
class CharProxy
{
public:
CharProxy(String& str_, int index_); //构造
CharProxy& operator=(const CharProxy& rhs_); //左值运用
CharProxy& operator=(char ch_); //左值运用
operator char() const; //右值运用
char* operator&();
const char* operator&() const;
private:
//记录所引用的String对象和当前字符的下标,
//在左值运用时,可以修改String对象的值
String& _theString;
int _charIndex;
};
const CharProxy operator[](int index) const;
CharProxy operator[](int index);
private:
struct StringValue : public RCObject
{
char* ptr;
StringValue(const char* str_);
StringValue(const StringValue& rhs_);
~StringValue();
private:
void init(const char* str_);
};
RCPtr _value;
};
RCObject::RCObject()
:refCount(0), shareable(true) //refCount初始化为0,其值完全有RCPtr控制
{
}
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;
}
template<typename T>
RCPtr::RCPtr(const RCPtr& rhs_)
: _ptr(rhs_._ptr)
{
init();
}
template<typename T>
void RCPtr::init()
{
if (_ptr == nullptr)
{
return;
}
if (_ptr->isShareable() == false)
{
//如果其值不可共享,那么就赋值一份
//注意,这里将新对象的赋值行为,由之前的
//String转移到RCPtr,此时T(在原有的String实现中是StringValue)
//若要完成深拷贝,T(StringValue)必须重写赋值运算符
_ptr = new T(*_ptr);
}
//计数器完全由RCPtr控制,即使上个if语句内重新赋值的对象
//其引用计数也由RCPtr控制,故将RCObject的refCount初值赋值0
_ptr->addReference();
}
template<typename T>
RCPtr& RCPtr::operator=(const RCPtr& rhs_)
{
if (_ptr != rhs_._ptr)
{
if (_ptr != nullptr)
{
_ptr->removeReference();
}
_ptr = rhs_._ptr;
init();
}
return *this;
}
template<typename T>
RCPtr::~RCPtr()
{
if (_ptr != nullptr)
{
_ptr->removeReference();
}
}
template<typename T>
T* RCPtr::operator->() const
{
return _ptr;
}
template<typename T>
T& RCPtr::operator*() const
{
return *_ptr;
}
const String::CharProxy String::operator[](int index_) const
{
//为了简洁,这里略去对下标的安全检查
return CharProxy(const_cast(*this), index_);
}
String::CharProxy String::operator[](int index_)
{
//将决定读写的权利交给CharProxy,这里当做读,简单的返回
//CharProxy实例
return CharProxy(*this, index_);
}
String::CharProxy::CharProxy(String& str_, int index_)
: _theString(str_), _charIndex(index_)
{
}
String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs_)
{
if (_theString._value->isShared())
{
_theString._value = new String::StringValue(_theString._value->ptr);
}
//不再需RCObject中的shareable变量及对应的成员函数
_theString._value->ptr[_charIndex] =
rhs_._theString._value->ptr[rhs_._charIndex];
return *this;
}
String::CharProxy& String::CharProxy::operator=(char ch_)
{
if (_theString._value->isShared())
{
_theString._value = new StringValue(_theString._value->ptr);
}
_theString._value->ptr[_charIndex] = ch_;
return *this;
}
String::CharProxy::operator char() const
{
return _theString._value->ptr[_charIndex];
}
char* String::CharProxy::operator&()
{
//确定"标记的字符"(本函数返回一个指针指向它)所属的字符串实值不为
//任何其它String对象共享(如果共享,就做一份专属副本出来)
if (_theString._value->isShared())
{
_theString._value = new StringValue(_theString._value->ptr);
}
//我们不知道clients会将本函数返回的指针保留多久,所以"目标字符"
//所属的字符串实值(一个StringValue对象)绝不可以被共享
_theString._value->markUnshareable();
return &(_theString._value->ptr[_charIndex]);
}
const char* String::CharProxy::operator&() const
{
return &(_theString._value->ptr[_charIndex]);
}
void String::StringValue::init(const char* str_)
{
ptr = new char[strlen(str_) + 1];
strcpy(ptr, str_);
}
String::StringValue::StringValue(const StringValue& rhs_)
{
init(rhs_.ptr);
}
String::StringValue::StringValue(const char* str_)
{
init(str_);
}
String::StringValue::~StringValue()
{
if (ptr != nullptr)
{
delete[] ptr;
}
}