一个引用计数的String类–The C++ Programming Language
一、String类描述
The C++ Programming Language书中展示了一个引用计数的String类作为练习。基本思想是值内容相同的对象之间共享一份内存中的字符串,类似于python的值管理机制。
String类有三个辅助类。struct Srep是实现值管理和引用计数的类,用来使字符串值在多个String类的实例之间共享。 class Cref实现了下标操作符,发生数组越界时将抛出class Range类。
下面是String类的具体设计:
class String {
Struct Srep; // actual representation
Srep* rep;
public:
class Cref; // reference to char
class Range {}; // for exceptions
// ... 其他成员函数
Cref operator[](int i); // 这里有针对下标操作符的特殊设计
char operator[](int i) cosnt;
// ... 其他成员函数
}; // class String
struct String::Srep {
char* s; // string representation
int sz; // string length
int n; // reference count
// 构造函数 ...
// 为值管理提供的接口
Srep* get_own_copy(); // 当一个String实例对应的字符串引用计数大于1时就不能原地修改,需要复制一份
void assign(int nsz, char* p); // 对一个复制过的Srep实例赋值
private:
// 定义没有实现的一个拷贝构造函数和一个过载赋值操作符来阻止复制
// 因为每个字符串值在内存中只有一份
Srep(const Srep&);
Srep& operator=(const Srep&);
}; // member class Srep
class String::Cref {
// 把String设置成Cref的友元类,使Cref能够访问String类的私有成员
friend class String;
String& s;
int index;
// 构造函数 ...
public:
// 配合下标操作符的接口
operator char() const;
void operator=(const char c);
}; // member class Cref
二、Srep类的值管理机制
引用计数的变化主要是两种情况:
- 当通过拷贝构造函数或过载的赋值操作符方式创建了一个String类实例时,相应的字符串实现*rep的引用计数要加一。
- 或者对一个String类实例s赋给字符串字面值时,如果原本s.rep的引用计数大于一,这时也需要创建一个副本、同时把引用计数减一。
Srep的成员函数get_own_copy是对创建副本的处理,assign是对字符串赋值的处理。
Srep* Srep::get_own_copy() {
if (n == 1) return this;
-- n;
return new Srep(sz, s); // 创建一个引用计数为1的副本
}
void Srep::assign(int nsz, const char* p) {
if (nsz != sz) {
delete [] s;
s = new char[nsz+1];
sz = nsz;
}
// 长度相同就直接写入
strcpy(s, p);
}
三、Cref类对下标操作符的设计
先仔细看看有关下标操作符的接口:
1. Cref String::operator[](int i); // 这里返回一个Cref对象
2. char String::operator[](int i) const; // 只读成员函数
3. Cref::operator char() const;
4. void Cref::operator=(const char c); // 注意这里是void
2和3都是只读成员函数,这说明要想通过下标操作符来修改String实例的某一个字符,必须走1->4的途径:s.operator[](i).operator=(‘c’),其他任何方式都不能修改String实例的内容。
也就是教材示例函数体中的第二行,见下图:
对于图中的const String& r,因为const对象只能调用只读成员函数,r可用的下标操作符只有2。
四、实现代码
String.h
#ifndef STRING_H_
#define STRING_H_
# include <iostream>
using std::ostream;
using std::istream;
class String {
struct Srep;
Srep* rep;
public:
class Cref;
class Range {};
String(); // x = ""
String(const char*); // x = "abc"
String(const String&); // x = other_string
String& operator=(const char*);
String& operator=(const String&);
~String();
void check(int i) const;
char read(int i) const;
void write(int i, char c);
Cref operator[](int i);
char operator[](int i) const;
int size() const;
// operators (stream operators can be written as member functions only)
// must be implement after struct Srep is defined
String& operator+=(const String&);
String& operator+=(const char*);
friend ostream& operator<<(ostream&, const String&);
friend istream& operator>>(istream&, String&);
friend bool operator==(const String& x, const char* s);
friend bool operator==(const String& x, const String& y);
friend bool operator!=(const String& x, const char* s);
friend bool operator!=(const String& x, const String& y);
String operator+(const String& y);
String operator+(const char* s);
};
#endif /* STRING_H_ */
String.cpp
# include <cstring>
# include <string>
# include "String.h"
using std::string;
// member class Srep
struct String::Srep {
char* s;
int sz, n;
Srep(int len, const char* ss): sz(len) {
n = 1; s = new char[sz+1];
strcpy(s, ss);
}
~Srep() { delete [] s; }
Srep* get_own_copy() { // necessary to clone when reference count greater than 1
if (n == 1) return this;
n --;
return new Srep(sz, s);
}
void assign(int nsz, const char* p) {
// n must be 1 ?
if (sz != nsz) {
delete [] s;
sz = nsz;
s = new char[nsz+1];
}
strcpy(s, p);
}
private: // no implement in order to prevent copying ?
Srep(const Srep&);
Srep& operator=(const Srep&);
};// member class Srep
// member class Cref
class String::Cref {
friend class String; // to enable access to Class String
String& s;
int index;
Cref(String& ss, int i): s(ss), index(i) {}
public:
operator char() const { return s.read(index); }
void operator=(char c) { s.write(index, c); }
};// class Cref END
// class String
String::String() {
char s[] = "";
rep = new Srep(0, s);
}
String::String(const char* s) {
rep = new Srep(strlen(s), s);
}
String::String(const String& x) {
x.rep->n ++;
rep = x.rep;
}
String::~String() {
std::cout <<"reference count: " <<rep->n;
// `delete [] rep is` incorrect (rep is a pointer to a Srep instance not an array)
if (-- rep->n == 0) delete rep;
std::cout <<" destructed" <<std::endl;
}
String& String::operator=(const char* s) {
if (strcmp(s, rep->s) != 0) {
// when rep->n == 1 return this to recycle rep
rep = rep->get_own_copy();
rep->assign(strlen(s), s);
}
return *this;
}
String& String::operator=(const String& x) {
x.rep->n ++;
if (-- rep->n == 0) delete [] rep;
rep = x.rep;
return *this;
}
void String::check(int i) const { if (i < 0 || rep->sz <= i) throw Range(); }
char String::read(int i) const { return rep->s[i]; }
void String::write(int i, char c) { rep = rep->get_own_copy(); rep->s[i]=c; }
String::Cref String::operator[](int i) { check(i); return Cref(*this, i); }
char String::operator[](int i) const { check(i); return rep->s[i]; }
int String::size() const { return rep->sz; }
String& String::operator +=(const String& x) {
int nsz = rep->sz + x.rep->sz;
char* s = new char[nsz+1];
s = strcat(rep->s, x.rep->s);
rep = rep->get_own_copy();
rep->assign(nsz, s);
return *this;
}
String& String::operator +=(const char* s) {
int nsz = rep->sz + strlen(s);
char *ss = new char[nsz+1];
rep = rep->get_own_copy();
rep->assign(nsz, ss);
return *this;
}
String String::operator +(const String& y) {
int nsz = rep->sz + y.rep->sz;
char *s = new char[nsz+1];
s = strcat(rep->s, y.rep->s);
return String(s);
}
String String::operator +(const char* ss) {
int nsz = rep->sz + strlen(ss);
char *s = new char[nsz+1];
s = strcat(rep->s, ss);
return String(s);
}
bool operator==(const String& x, const char* s) { return strcmp(x.rep->s, s) == 0; }
bool operator==(const String& x, const String& y) { return strcmp(x.rep->s, y.rep->s) == 0; }
bool operator!=(const String& x, const char* s) { return strcmp(x.rep->s, s); }
bool operator!=(const String& x, const String& y) { return strcmp(x.rep->s, y.rep->s); }
ostream& operator<<(ostream& out, const String& s) {
out.clear();
out <<string(s.rep->s);
// std::cout <<"Completed" <<std::endl;
return out;
}
istream& operator>>(istream& in, String& s) {
string str;
getline(in, str);
s.rep = s.rep->get_own_copy();
s.rep->assign(str.size(), str.c_str());
return in;
}
// class String END
参考资料
[1]. The C++ Programming Language, 3rd Edition Stroustrup B.