条款14:缓式优化,之二:(1)
引入缓式优化 难度:3
copy-on-write是一种常用的优化技术,你知道如何实现它吗?
Original::String(见条款13)好倒是好,但有时候,在得到一个字符串对象的复制对象后,用户可能不会在使用中对它做任何修改,然后又把它丢弃了。
"这好像有点丢人,"条款13中Original::String的设计者可能会对自己不 满,"我每次都做了分配新缓冲区的所有工作(开销会很昂贵),可是,如果所有用户只是从新字符串中读取数据然后将其摧毁,那么,我所做的其实完全没有必要。我可以只是让两个字符串对象在底层共享一个缓冲区,暂时避免复制操作;只是在确实知道需要复制的时候,也就是,当其中一个对象试图修改这个字符串的时候,我才进行复制。采用这种方法,如果用户永远不修改这个复制对象,我就永远不用做额外的工作!"
脸上带着微笑,眼里充满着坚定,这个程序员设计了一个Optimized::String,他使用了一个copy-on-write实现(也称"缓式复制"),对底层字符串实体实施引用计数:
- namespace Optimized
- {
- class StringBuf
- {
- public:
- StringBuf(); // 开始为空
- ~StringBuf(); // 删除缓冲区
- void Reserve( size_t n ); // 保证len >= n
-
- char* buf; // 分配的缓冲区
- size_t len; // 缓冲区长度
- size_t used; // 实际使用的字符数量
- unsigned refs; // 引用计数
-
- private:
- // 禁止复制 ...
- //
- StringBuf( const StringBuf& );
- StringBuf& operator=( const StringBuf& );
- };
-
- class String
- {
- public:
- String(); // 开始为空
- ~String(); // 递减引用计数
- //(如果refs==0则删除缓冲区)
- String( const String& ); // 指向同一缓冲区
- // 并递增引用计数
- void Append( char ); // 增添一个字符
-
- // ...省略operator=()等...
-
- private:
- StringBuf* data_;
- };
- }
你的任务是:实现Optimized::StringBuf和Optimized::String。 你可能需要增加一个私有的String::AboutToModify()辅助函数,以简化处理逻辑。
解答
首先看看StringBuf。注意,默认的以成员为单位的复制和赋值操作对于StringBuf来说没有意义,所以二者都被禁止掉了(声明为private但没有定义)。
这里,Optimized::StringBuf所完成的工作正是当初Original::String所做工作的一部分。StringBuf的默认构造函数和析构函数正如你所估计的那样:
- namespace Optimized
- {
-
- StringBuf::StringBuf() : buf(0), len(0), used(0), refs(1) { }
-
- StringBuf::~StringBuf() { delete[] buf; }
- Reserve()也和 Original::String中的那个类似:
- void StringBuf::Reserve( size_t n )
- {
- if ( len < n )
- {
- size_t newlen = max ( len* 1.5, n );
- char* newnewbuf = new char[ newlen ];
- copy( buf, buf+used, newbuf );
-
- delete[] buf; // 现在,所有实际工作已经完成,
- buf = newbuf; // 所以,获得拥有权
- len = newlen;
- }
- }
StringBuf就这些。
再看看String本身。默认构造函数很容易实现:
- String::String() : data_(new StringBuf) { }