一. 使用引用计数来避免多分拷贝
class String
{
private:
class String_rep
{
public:
int use_count;
char* chars;
String_rep(const char*);
String_rep(const String_rep&);
~String_rep();
void increment();
void decerement();
const String_rep& operator=(const String_rep&);
};
String_rep* rep;
public:
String(const char* = "");
~String();
const String& operator=(const String&);
};
此处String_rep是一个嵌套类:它声明在String类中。由于我们将它声明在String的使用段中,所以它只能被 String 的成员调用。
也许有的编译器不支持嵌套类,可以用如下方法:将String_rep声明为非嵌套类,它里面的所有成员(包括构造函数)都被声明为
私有,然后将 String 类声明为他的友类:
class String_rep
{
private:
friend class String;
int use_count;
char* chars;
String_rep(const char*);
String_rep(const String_rep&);
~String_rep();
void increment();
void decerement();
const String_rep& operator=(const String_rep&);
};
这样可以保证用户无法构造 String_rep 的对象,但带来的弊端是:我们必须把String_rep放置到全局的名字空间中去。
1.String_rep 的实现
String_rep 的构造函数很简单:它会为字符串申请空间,将字符串复制到该空间中去,然后把计数器置为0.它的析构函数
会释放掉构造函数中所申请的内存空间。
String::String_rep::String_rep(const char* cp)
:use_count(0)
,chars(new char[strlen(cp) + 1])
{
strcpy(chars, cp);
}
String ::String_rep::~String_rep()
{
delete[] chars;
}
void String::String_rep::increment()
{
++use_count;
}
void String::String_rep::decerement()
{
if(--use_count == 0){
delete[] this;
}
}
由于 increment() 和 decerement() 这两个方法都很小,且经常使用,最好将它们声明为 inline 函数。
String::String_rep::String_rep(const String_rep& orig)
:use_count(0)
,chars(new char[strlen(orig.chars) + 1])
{
strcpy(chars, orig.chars);
}
const String::String_rep& String::String_rep operator=(const String_rep& orig)
{
if(this != &orig){
delete [] this;
chars = new char[strlen(orig.chars) + 1];
strcpy(chars, orig.chars);
}
return *this;
}
2.String 的实现
在String类中,必须维护String_rep中的使用计数器。这包含两部分:在创建String对象时调用increment();
以及在摧毁String时调用decerement();
String::String(const char* c)
:rep(new String_rep(c))
{
rep->increment();
}
String::~String()
{
rep->decerement();
}
String::String(const String& s )
:rep(s.rep)
{
rep->increment();
}
任何改变 String::rep 值的操作都必须对计数器进行同时更新:
const String& String::operator=(const String& s)
{
if(rep != s.rep){
rep->decerement();
rep = s.rep;
rep->increment();
}
}
我们必须在使用赋值操作符时仔细检测 s = s 这种情况,以避免该操作中调用的 decerement()会将 rep 删除。
3.写时复制(Copy on write)
两个或多个String 共享同一个rep的事实只是我们实现中的细节;我们必须确保所有会导致String值改变的操作
不会导致其他共享该rep 的String对象的值也发生改变。
解决方法:在这种操作中分配一个新的 rep,让它来维护被更改后的值。
#include <ctype.h>
void String::to_lower()
{
if(rep->use_count > 1){
String_rep* new_rep = new String_rep(rep->chars);
rep->decerement();
rep = new_rep;
rep->increment();
}
assert(rep->use_count == 1);
for(char* cp = rep->chars; *cp; ++cp){
*cp = tolower(*cp);
}
}
注意:
使用了计数器的类要比没有使用计数器但功能相同的类要复杂的多,并且所有围绕着该计数器的操作都导致程序执行的时间显著增加。
经比较的结论是:使用了计数器的版本在某些程序(其中对String的平均赋值超过一次)中要快一些,在其他程序中则要慢些。