跳转到内容

更多 C++ 惯用法/写时复制

来自维基教科书,为开放世界提供开放书籍

写时复制

[编辑 | 编辑源代码]

实现延迟复制优化。就像延迟初始化一样,只在需要的时候才执行操作,以提高效率。

也称为

[编辑 | 编辑源代码]
  • COW (写时复制)
  • 延迟复制

复制对象有时会导致性能下降。如果对象经常被复制,但在以后很少被修改,写时复制可以提供显著的优化。为了实现写时复制,一个指向实际内容的智能指针用于封装对象的价值,并且在每次修改时,都会检查对象的引用计数;如果对象被引用多次,则会在修改之前创建内容的副本。

解决方案和示例代码

[编辑 | 编辑源代码]
#ifndef COWPTR_HPP
#define COWPTR_HPP

#include <memory>

template <class T>
class CowPtr
{
    public:
        typedef std::shared_ptr<T> RefPtr;

    private:
        RefPtr m_sp;

        void detach()
        {
            T* tmp = m_sp.get();
            if( !( tmp == 0 || m_sp.unique() ) ) {
                m_sp = RefPtr( new T( *tmp ) );
            }
        }

    public:
        CowPtr(T* t)
            :   m_sp(t)
        {}
        CowPtr(const RefPtr& refptr)
            :   m_sp(refptr)
        {}
        const T& operator*() const
        {
            return *m_sp;
        }
        T& operator*()
        {
            detach();
            return *m_sp;
        }
        const T* operator->() const
        {
            return m_sp.operator->();
        }
        T* operator->()
        {
            detach();
            return m_sp.operator->();
        }
};

#endif

这种写时复制的实现是通用的,但除了必须通过智能指针解除引用来引用内部对象的不便之外,它至少有一个缺点:那些返回对其内部状态引用的类,比如

char & String::operator[](int)

可能会导致意想不到的行为。[1]

考虑以下代码片段

CowPtr<String> s1 = "Hello";
char &c = s1->operator[](4); // Non-const detachment does nothing here
CowPtr<String> s2(s1); // Lazy-copy, shared state
c = '!'; // Uh-oh

最后一行代码的目的是修改原始字符串s1,而不是副本,但作为副作用,s2也意外被修改了。

一个更好的方法是编写一个自定义的写时复制实现,该实现封装在我们想要延迟复制的类中,对用户来说是透明的。为了解决上述问题,可以将那些已经将内部状态的引用传递出去的对象标记为“不可共享”——换句话说,强制复制操作深度复制对象。作为优化,可以在任何不传递内部状态引用(例如,void string::clear())的非常量操作之后将对象恢复为“可共享”,因为客户端代码期望这些引用会被无效化。[1]

已知用途

[编辑 | 编辑源代码]
[编辑 | 编辑源代码]

参考资料

[编辑 | 编辑源代码]
  1. a b Herb Sutter,《更优秀的 C++》,Addison-Wesley 2002 年 - 第 13-16 条
华夏公益教科书