标准库中 string 的底层实现方式
三种基本实现方式
Eager Copy - 深拷贝
为 Ubuntu 1804 中 string 的实现方法
tip:指针大小为 32
测试代码
// Ubuntu 1804 中
string s1 = "1234";
cout << sizeof(s1) << endl;
// 输出结果为:32 - 指针的大小
实现图例
COW(Copy On Write) - 写时复制
具体实现方式:浅拷贝 + 引用计数 - 为 Ubuntu 1404 中 string 的实现方法
tip:指针大小为 8
测试代码
// Ubuntu 1404 中
string s1 = "1234";
cout << sizeof(s1) << endl;
// 输出结果为:8 - 指针的大小
实现图例
实现代码
// 自定义类型 String 实现浅拷贝
// 具体思路:创建数组时,在头部额外增加 4 个字节,用来存放引用计数
// 具体内容从第五个字节开始存储
SSO(Short String Optimization) - 短字符串优化
tips:
-
当字符串的长度小于 15 字节的时候,存在栈上
-
当字符串的长度大于 15 字节的时候,存在堆上。
-
指针大小为 32
测试代码
#include <iostream>
#include <string>
using std::string;
using std::cout;
using std::endl;
int main()
{
string s1 = "123";
string s2 = "qwertyuiopasdfghjkzxcvbnm";
printf("&s1:%p\n", s1.c_str());
printf("&s2:%p\n", s2.c_str());
cout << sizeof(s1) << endl;
return 0;
}
// 运行结果:
// 0x7ffcbb249380 - 栈
// 0x559942d02e70 - 堆
// 32 - 指针的大小
实现图例
COW 的实现 - 引用计数
参考的为《More Effective C++》
简易版本
重载使用下标访问符时,忽略判断读写区别
使用代理类可进行判断
实现代码
String_Refcount.h
#ifndef __STRING_REFCOUNT_H
#define __STRING_REFCOUNT_H
// 引用计数
// 1. 引用计数允许多个有相同值的对象共享这个值的实现。
// 节省内存,而且使得程序运行更快,因为不需要构造和析构这个值的拷贝。
// 2. 使用引用计数后,对象自己拥有自己,当没人再使用它是,会自动销毁自己。
// 因此,引用计数是个简单的垃圾回收体系。
#include <cstring>
// 实现引用计数
class String
{
public:
String(const char *initValue = ""); // 构造函数
String(const String& rhs); // 拷贝构造函数
String& operator=(const String& rhs); // 赋值运算符
~String(); // 析构函数
// 下标访问符 - 有待改进:区分读写
const char& operator[](size_t idx) const;
char& operator[](size_t idx);
private:
// 保存引用计数及其跟踪的值
struct StringValue
{
StringValue(const char *initValue);
~StringValue();
int refCount;
char *data;
// 当有复数个对象指向同一内存块时,将指针直接指向内存块时,将会对所有指向该内存块的对象值进行修改
// 解决方案:
// 增加标志 shareable 指出是否可共享。
// 1. 创建时/可共享时,将标志打开
// 2. 在非 const 的 operator[] 被调用时将它关闭,之后将永远保持这个状态
bool shareable;
};
StringValue *value;
};
#endif // __STRING_REFCOUNT_H
String_Refcount.cc
#include <cstring>
#include "String_Refcount.h"
String::StringValue::StringValue(const char *initValue)
: refCount(1)
, shareable(true)
{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}
String::StringValue::~StringValue()
{
delete[] data;
data = nullptr;
}
// 用构造函数创建对象时,都是各自独立的
// String s1("More Effective C++")
// [s1] ----> [1] ----> [More Effective C++]
// String s2("More Effective C++")
// [s2] ----> [1] ----> [More Effective C++]
// 可以改进,即创建时,跟踪已存在的 StringValue 对象,只有不同串时才创建新的对象
// 有缘在写!
String::String(const char *initValue)
: value(new StringValue(initValue))
{}
// 实现拷贝构造函数
// String s1("More Effective C++")
// String s2 = s1;
// [s1] --
// --> [2] ----> [More Effective C++]
// [s2] --
String::String(const String& rhs)
{
if(rhs.value->shareable)
{
value = rhs.value;
++value->refCount;
}
else
value = new StringValue(rhs.value->data);
}
// 重载赋值运算符
// String s1("More Effective C++")
// String s2("Nothing");
// s1 = s2;
String& String::operator=(const String& rhs)
{
if(value == rhs.value)
return *this;
if(0 == --value->refCount)
{
delete value;
value = nullptr;
}
if(rhs.value->shareable)
{
value = rhs.value;
++value->refCount;
}
else
value = new StringValue(rhs.value->data);
return *this;
}
// 实现析构函数
String::~String()
{
if(0 == --value->refCount)
{
delete value;
value = nullptr;
}
}
// 由于是 const 版本,不允许更改其值,所以直接返回即可
const char& String::operator[](size_t idx) const
{
return value->data[idx];
}
// 由于无法直接区分读写,所以非 const 的下标访问符重载时,只要调用就默认认为要更改其值
// 可再由一个内部类来实现区分读写
char& String::operator[](size_t idx)
{
// 如果该对象有复数个引用,则新建一个对象
if(1 != value->refCount)
{
--value->refCount;
value = new StringValue(value->data);
}
value->shareable = false;
return value->data[idx];
}
简易版本的改进
设置基类 RCObject,任何需要引用计数的类都必须从它继承,String 再继承 RCObject。
再创建灵巧指针模板 RCPtr,实现指针自己检测拷贝指针、给指针赋值和销毁指针等工作
实现代码
RCPtr.h
#ifndef __RCPTR_H
#define __RCPTR_H
// 实现自动的引用计数处理
template<class T>
class RCPtr
{
public:
RCPtr(T *realPtr = 0);
RCPtr(const RCPtr& rhs);
RCPtr& operator=(const RCPtr& rhs);
~RCPtr();
T *operator->() const;
T& operator*() const;
private:
T *pointee;
void init();
};
#endif // __RCPTR_H
RCPtr.cc
#include "RCPtr.h"
template<class T>
RCPtr<T>::RCPtr(T *realPtr)
: pointee(realPtr)
{
init();
}
template<class T>
RCPtr<T>::RCPtr(const RCPtr& rhs)
: pointee(rhs.pointee)
{
init();
}
template<class T>
void RCPtr<T>::init()
{
if(0 == pointee)
return;
if(false == pointee->isShareable())
pointee = new T(*pointee);
pointee->addReference();
}
template<class T>
RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)
{
if(rhs.pointee != pointee)
{
if(pointee)
pointee->removeReference();
pointee = rhs.pointee;
init();
}
return *this;
}
template<class T>
RCPtr<T>::~RCPtr()
{
if(pointee)
pointee->removeReference();
}
template<class T>
T *RCPtr<T>::operator->() const
{
return pointee;
}
template<class T>
T& RCPtr<T>::operator*() const
{
return *pointee;
}
RCObject.h
#ifndef __RCOBJECT_H
#define __RCOBJECT_H
#include <iostream>
#include "RCPtr.h"
class RCObject
{
protected:
RCObject();
RCObject(const RCObject& rhs);
RCObject& operator=(const RCObject& rhs);
// 虚函数 - 详情见 theVirtual.cc
virtual ~RCObject() = 0;
public:
void addReference();
void removeReference();
void markUnshareable();
bool isShareable() const;
bool isShared() const;
private:
int refCount;
bool shareable;
};
#endif // __RCOBJECT_H
RCObject.cc
#include "RCObject.h"
RCObject::RCObject()
: refCount(0) // 初始化列表中设置为 0,在构造后,将构造它的对象简单地将 refCount 设置为 1 即可
, shareable(true)
{}
RCObject::RCObject(const RCObject&)
: refCount(0) // 同上,构造者负责将 refCount 设为正确的值
, shareable(true)
{}
// 不期望调用该函数,所以没有做任何事情
// RCObject 是基于引用计数来共享的值对象的积累,不该从一个赋给另外一个,
// 而应该是拥有这个值的对象被从一个赋给另外一个
// 有一说一,这理由没懂!
RCObject& RCObject::operator=(const RCObject&)
{
return *this;
}
RCObject::~RCObject() {}
void RCObject::addReference()
{
++refCount;
}
void RCObject::removeReference()
{
if(0 == --refCount)
delete this;
}
void RCObject::markUnshareable()
{
shareable = false;
}
bool RCObject::isShareable() const
{
return shareable;
}
bool RCObject::isShared() const
{
return refCount > 1;
}
String.h
#ifndef __STRING_REFCOUNT_H
#define __STRING_REFCOUNT_H
// 引用计数
// 1. 引用计数允许多个有相同值的对象共享这个值的实现。
// 节省内存,而且使得程序运行更快,因为不需要构造和析构这个值的拷贝。
// 2. 使用引用计数后,对象自己拥有自己,当没人再使用它是,会自动销毁自己。
// 因此,引用计数是个简单的垃圾回收体系。
#include <cstring>
#include "RCObject.h"
// 实现引用计数
class String
{
public:
String(const char *initValue = ""); // 构造函数
/* String(const String& rhs); // 拷贝构造函数 */
/* String& operator=(const String& rhs); // 赋值运算符 */
/* ~String(); // 析构函数 */
// 下标访问符 - 有待改进:区分读写
const char& operator[](size_t idx) const;
char& operator[](size_t idx);
private:
// 保存引用计数及其跟踪的值
struct StringValue
: public RCObject
{
StringValue(const char *initValue);
// 所有含有指针的类都应当提供拷贝构造函数 和 赋值运算
// 可以设置虚拷贝构造函数,实现当 pointee 指向 T 的派生类时,即
// struct SpecialStringValue: public StringValue { ... }
// RCPtr<String Value> tmp = new SpecialStringValue;
// 希望 pointee = new T(*pointee); // 生成一个 SpecialStringValue 的指针
// 但对于该 String 类,不期望生成 StringValue 的派生子类,所以忽略该问题
StringValue(const StringValue& rhs);
~StringValue();
char *data;
void init(const char *initValue);
};
/* StringValue *value; */
RCPtr<StringValue> value;
};
#endif // __STRING_REFCOUNT_H
String.cc
#include <cstring>
#include "String_Refcount.h"
void String::StringValue::init(const char *initValue)
{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}
String::StringValue::StringValue(const char *initValue)
/* : refCount(1) */
/* , shareable(true) */
{
/* data = new char[strlen(initValue) + 1]; */
/* strcpy(data, initValue); */
init(initValue);
}
String::StringValue::~StringValue()
{
delete[] data;
data = nullptr;
}
String::StringValue::StringValue(const StringValue& rhs)
{
/* data = new char[strlen(rhs.data) + 1]; */
/* strcpy(data, rhs.data); */
init(rhs.data);
}
// 用构造函数创建对象时,都是各自独立的
// String s1("More Effective C++")
// [s1] ----> [1] ----> [More Effective C++]
// String s2("More Effective C++")
// [s2] ----> [1] ----> [More Effective C++]
// 可以改进,即创建时,跟踪已存在的 StringValue 对象,只有不同串时才创建新的对象
// 有缘在写!
String::String(const char *initValue)
: value(new StringValue(initValue))
{}
/* // 实现拷贝构造函数 */
/* // String s1("More Effective C++") */
/* // String s2 = s1; */
/* // [s1] -- */
/* // --> [2] ----> [More Effective C++] */
/* // [s2] -- */
/* String::String(const String& rhs) */
/* { */
/* if(rhs.value->shareable) */
/* { */
/* value = rhs.value; */
/* ++value->refCount; */
/* } */
/* else */
/* value = new StringValue(rhs.value->data); */
/* } */
/* // 重载赋值运算符 */
/* // String s1("More Effective C++") */
/* // String s2("Nothing"); */
/* // s1 = s2; */
/* String& String::operator=(const String& rhs) */
/* { */
/* if(value == rhs.value) */
/* return *this; */
/* if(0 == --value->refCount) */
/* { */
/* delete value; */
/* value = nullptr; */
/* } */
/* if(rhs.value->shareable) */
/* { */
/* value = rhs.value; */
/* ++value->refCount; */
/* } */
/* else */
/* value = new StringValue(rhs.value->data); */
/* return *this; */
/* } */
/* // 实现析构函数 */
/* String::~String() */
/* { */
/* if(0 == --value->refCount) */
/* { */
/* delete value; */
/* value = nullptr; */
/* } */
/* } */
// 由于是 const 版本,不允许更改其值,所以直接返回即可
const char& String::operator[](size_t idx) const
{
return value->data[idx];
}
/* // 由于无法直接区分读写,所以非 const 的下标访问符重载时,只要调用就默认认为要更改其值 */
/* // 可再由一个内部类来实现区分读写 */
/* char& String::operator[](size_t idx) */
/* { */
/* // 如果该对象有复数个引用,则新建一个对象 */
/* if(1 != value->refCount) */
/* { */
/* --value->refCount; */
/* value = new StringValue(value->data); */
/* } */
/* value->shareable = false; */
/* return value->data[idx]; */
/* } */
char& String::operator[](size_t idx)
{
if(value->isShared())
value = new StringValue(value->data);
value->markUnshareable();
return value->data[idx];
}
自己写的版本,且实现了取下标时判断读写 - 代理类
通过代理类来帮助区分通过 operaotr[]
进行的是读操作还是写操作
核心思想
-
无法在
operator[]
内判断进行的是读还是写操作,这是确定的。所以,返回类型更改为一个代理类 -proxy
类,将判断读还是写的行为推迟到我们知道operator[]
的结果被怎么使用。 - lazy 原则 - 《More Effective C++》 ( Item M17 )其中 proxy 所做的事
- 创建它,也就是指定它扮演哪个字符。
- 将它作为赋值操作的目标,在这种情况下可以将赋值真正作用在它扮演的字符上。当这样被使用时,
proxy
类扮演的是左值。 - 用其它方式使用它。这时,
proxy
类扮演的是右值。
-
通过
proxy
类所做的事,在配合隐式转换,从而达到自动右值的扮演。具体流程
- 对于
String
的 取下标操作,返回了CharProxy
对象,而没有为这样的对象定义输出流操作,编译器自动地寻找一个隐式的类型转换以使得operat<<
调用成功。 CharProxy
类内部申明了一个隐式转换到cahr
的操作。- 当取下标后作为右值时,都故意以这种隐式转换的方式去执行调用。
- 对于
-
在
CharProxy
类中重写operator=(char);
,使得从String
返回CharProxy
类型的返回值后直接作为左值进行赋值。 -
const
版本的本就不可更改,故不存在写操作。主要实现代码
// String 类中的修改 class String { // ... public: class CharProxy { public: CharProxy(String&, size_t); // creation CharProxy& operator=(const CharProxy&); // lvalue CharProxy& operator=(char); // uses // 由自定义类型向其他类型转换,详情见 - 运算符重载 operator char() const; // rvalue // 改进项 - 无缝替换 // 1. 由于 String::operator[] 返回类型为 CharProxy // 所以当执行 // String s1 = "Hello"; // char *p = &s1[1]; // error // 因为没有 CharProxy * 到 char * 的转换函数 // 故,重载 CharProxy 类的取地址运算 char *operator&(); const char *operator&() const; // 2. 还有一些较为复杂的左值操作函数需要一一重载 // 如:operator++, operator+= 等等 // 3. 有当形参类型为 char& 时也会出现异常 // void swap(char&, char&); // String s = "C++"; // swap(s[0], s[1]); // 因为 String::operator[] 返回的是一个 CharProxy 对象 // CharProxy 对象可以隐式转换为一个 char,但是并没有转换为 char& 的转换函数 // 而转换而成的 char 是一个临时变量,不可也不引导作为非 const 的引用的的形参 // 4. 一些未定义的隐式转换,如 int 等 // 解决方案:申明需要隐式转换而生成的对象的类的构造函数为 explicit // 以使得第一次调用构造函数的行为在编译期间直接失败 // 详情见 - 《More Effective C++》 ( Item M5 ) private: String& theString; int charIndex; }; // 重写 String 类的 operator[] const CharProxy operator[](std::size_t idx) const; CharProxy operator[](std::size_t idx); // ... // 由于 CharProxy::operator=(char) 中要使用 String 的私有成员,所以设置为友元类 friend class CharProxy; private: // 智慧指针,非必须 - 实现代码中未使用智慧指针 RCPtr<StringValue> value; }; // 具体实现 // String 类的重写 const String::CharProxy String::operator[](size_t idx) const { return CharProxy(const_cast<String&>(*this), idx) } String::CharProxy String::operator[](size_t idx) {return CharProxy(*this, idx) } // String::CharProxy 的实现 String::CharProxy::CharProxy(String& str, size_t idx) :theString(str), charIndex(idx) {} // rvalue String::CharProxy::operator char() const { return theString.value->data[charIndex] } // lvalue String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs) { if(theString.value->isShared()) theString.value = new StringValue(theString.value->data); theString.value->data[charIndex] = rhs.theString.value->data[rhs.charIndex]; return *this; } String::CharProxy& String::CharProxy::operator=(char c) { if(theString.value->isShared()) theString.value = new StringValue(theString.value->data); theString.value->data[charIndex] = c; return *this; } const char *String::CharProxy::operator&() const { return &(theString.value->data[charIndex]) } char *String::CharProxy::operator&() { if(theString.value->isShared()) theString.value = new StringValue(theString.value->data); theString.value->markUnshareable(); return &(theString.value->data[charIndex]); }
实现代码
手动判断指针是否该删除
string_cow.cc
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
class String
{
friend std::ostream &operator<<(std::ostream &os, const String &rhs);
friend class CharProxy;
public:
class CharProxy
{
public:
CharProxy(String &str, int index);
CharProxy &operator=(const CharProxy &rhs);
CharProxy &operator=(char c);
operator char() const;
const char* operator&() const;
char* operator&();
private:
String &theString;
int charIndex;
};
const CharProxy operator[](int) const;
CharProxy operator[](int);
String()
: _pstr(new char[5]() + 4)
{
/* cout << "String" << endl; */
// operator top 4 Byte
// Because Integer is 4 Byte
initRefcount();
}
// String s1 = "Hello";
String(const char *pstr)
: _pstr(new char[strlen(pstr) + 5] + 4)
{
/* cout << "String(const char *)" << endl; */
strcpy(_pstr, pstr);
initRefcount();
}
// String s2 = s1;
String(const String &rhs)
: _pstr(rhs._pstr) // shallow copy
{
/* cout <<"String(const String &)" << endl; */
increaseRefcount();
}
// s3 = s1;
String &operator=(const String &rhs)
{
/* cout << "String &operator=(const String &)" << endl; */
if(this != &rhs) // copy itself
{
decreaseRefcount(); // release the Refcouont
_pstr = rhs._pstr; // shallow copy
increaseRefcount();
}
return *this;
}
~String()
{
/* cout << "~String" << endl; */
decreaseRefcount();
}
/* char &operator[](size_t idx) */
/* { */
/* if(idx < size()) */
/* { */
/* // 由于取下标后,可能会对该位置进行赋值操作,所以需要进行深拷贝 */
/* if(getRefcount() != 1) */
/* { */
/* char *ptmp = new char[size() + 5]() + 4; */
/* strcpy(ptmp, _pstr); */
/* decreaseRefcount(); */
/* _pstr = ptmp; */
/* initRefcount(); */
/* } */
/* return _pstr[idx]; */
/* } */
/* else */
/* { */
/* static char nullchar = '\0'; */
/* return nullchar; */
/* } */
/* } */
int getRefcount()
{
return *(int *)(_pstr- 4);
}
const char *c_str() const
{
return _pstr;
}
size_t size() const
{
return strlen(_pstr);
}
private:
void initRefcount()
{
*(int *)(_pstr - 4) = 1;
}
void increaseRefcount()
{
++*(int *)(_pstr - 4);
}
void decreaseRefcount()
{
--*(int *)(_pstr - 4);
if(0 == getRefcount())
delete[] (_pstr - 4);
}
char *_pstr;
};
const String::CharProxy String::operator[](int idx) const
{
return CharProxy(const_cast<String&>(*this), idx);
}
String::CharProxy String::operator[](int idx)
{
return CharProxy(*this, idx);
}
String::CharProxy::CharProxy(String& str, int idx)
: theString(str)
, charIndex(idx)
{}
String::CharProxy::operator char() const
{
return theString._pstr[charIndex];
}
String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs)
{
if(theString.getRefcount() != 1)
{
char *pString = theString._pstr;
char *ptmp = new char[strlen(pString) + 5]() + 4;
strcpy(ptmp, pString);
theString.decreaseRefcount();
theString._pstr = ptmp;
theString.initRefcount();
}
}
String::CharProxy& String::CharProxy::operator=(char c)
{
if(theString.getRefcount() != 1)
{
char *pString = theString._pstr;
char *ptmp = new char[strlen(pString) + 5]() + 4;
strcpy(ptmp, pString);
theString.decreaseRefcount();
theString._pstr = ptmp;
theString.initRefcount();
}
theString._pstr[charIndex] = c;
return *this;
}
const char* String::CharProxy::operator&() const
{
return &(theString._pstr[charIndex]);
}
char* String::CharProxy::operator&()
{
if(theString.getRefcount() != 1)
{
char *pString = theString._pstr;
char *ptmp = new char[strlen(pString) + 5]() + 4;
strcpy(ptmp, pString);
theString.decreaseRefcount();
theString._pstr = ptmp;
theString.initRefcount();
}
return &(theString._pstr[charIndex]);
}
std::ostream &operator<<(std::ostream &os, const String &rhs)
{
if(rhs._pstr)
os << rhs._pstr << endl;
return os;
}
int main()
{
cout << "First step:" << endl;
String s1 = "123";
String s2 = "321";
String s3 = s1;
printf("s1:%p\n", s1.c_str());
printf("s2:%p\n", s2.c_str());
printf("s3:%p\n", s3.c_str());
cout << endl << "Second step:" << endl;
s2 = s1;
s3[0];
s1[0] = '3';
printf("s1:%p\n", s1.c_str());
printf("s2:%p\n", s2.c_str());
printf("s3:%p\n", s3.c_str());
return 0;
}