[OOD-More C++ Idioms] 写时拷贝 (Copy on Write)

目的

达到延迟拷贝(lazy copy)的优化目的。和延迟初始化(lazy initialization)相似, 选择在恰当的时机更加有效。

别名

  • COW (copy-on-write)
  • Lazy copy

动机

拷贝对象有时会带来性能损失(performance penalty)。如果对象经常拷来拷去,但以很少修改,copy-on-write就能明显地提升性能。为了实现copy-on-write, 需要使用一个智能指针将真正的对象值封装起来,每次修改时都要检查一下对象的引用计数。如果对象被多次引用,就在修改前创建一个复本。

解决方案及示例

#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
译注:原文代码使用boost库,都改为std的实现了。

这是一个简单的实现版本。除了必须通过智能指针解引用(dereferencing)来引用其内部对象有点不太方便外,还至少有一个缺点:类可以返回内部状态的引用:

char & String::operator[](int)

这样会带有一些无法预期的行为。

考虑下面的代码段:

CowPtr<std::string> s1 = new std::string("Hello");
char &c = s1->operator[](4); // 非常量的detach操作什么也不做
CowPtr<std::string> s2(s1); // 延迟拷贝,共享的状态
c = ‘!‘; // 悲催啦

最后一行原本要修改原始的字串s1, 而不是它的复本s2,而事实上s2也被修改了。

一个比较好的做法是写一个自定义的copy-on-write实现,封装需要延时拷贝(lazy-copy)的类,并且保持对用户透明。为了解决上面的问题,可以标记对象为”不可共享(unshareable)”状态表示已经交出了对内存对象的引用,也就是强制进行深度拷贝。进一步优化,可以在那些不会放弃内部对象引用的non-const操作后恢复为”共享(shareable)”状态,(比如, `void string::clear())),因为客户端代码期望这些引用都会失效。

译注:这一部分说得不清楚。标记对象为不可共享,比如上面例子中,取出字符c后设为不可共享,再建构s2时直接进行深拷贝。另外说在non-const操作没有放弃内部对象,指的是这类操作创建了一个复本,这时候的原来的对象可以更新为shareable。

已知的应用

相关的惯用法

参考



更多翻译内容请访问Github项目

时间: 2024-12-23 11:14:06

[OOD-More C++ Idioms] 写时拷贝 (Copy on Write)的相关文章

写时拷贝(copy on write)

;写时拷贝(copy on write) 即某个对象的堆指针成员平时和其他对像的堆指针成员平时它们几个共享同一份实值 ,知道这个独享的值必须发生改写的时候才停止共享,而将该值重新申请一份空间存放自己私有的东西

c++ 写时拷贝

写时拷贝--Copy On Writ #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class String { public:  String(char *str = "") :_str(new char[strlen(str)+5])  {   _str += 4;   _GetRefCount(_str) = 1;   strcpy(_str, str);  }  Str

写时拷贝(copy-on-write) COW技术

时间:2014.05.06 地点:基地二楼 ---------------------------------------------------------------------------------- 一.写时拷贝的概念--COW技术在Linux进程上的应用 Linux在使用fork()函数进程创建时,传统fork()的做法是系统把所有的资源复制给新创建的进程,这种方式不仅单一,而且效率低下.因为所拷贝的数据或别的资源可能是可以共享的.现在Linux的fork()使用写时拷贝页来实现新进

分析 写时拷贝 的四个方案(Copy On Write)

深拷贝效率低,我们可以应引用计数的方式去解决浅拷贝中析构多次的问题. 首先要清楚写时拷贝是利用浅拷贝来解决问题!! 方案一 class String { private: char* _str; int _refCount; }; 方案一最不靠谱,它将用作计数的整形变量_refCount定义为类的私有成员变量,任何一个对象都有它自己的成员变量_refCount,它们互不影响,只要拷贝出了对象,_refCount大于了1,那么每个对象调用自己的析构函数时--_refCount不等于0,那么它们指向

写时拷贝技术

Copy On Write(COW):写时拷贝技术 一.什么是写时拷贝技术: 写时拷贝技术可以理解为"写的时候才去分配空间",这实际上是一种拖延战术. 举个栗子: 二.写时拷贝技术原理: 写时拷贝技术是通过"引用计数"实现的,在分配空间的时候多分配4个字节,用来记录有多少个指针指向块空间,当有新的指针指向这块空间时,引用计数加一,当要释放这块空间时,引用计数减一(假装释放),直到引用计数减为0时才真的释放掉这块空间.当有的指针要改变这块空间的值时,再为这个指针分配自

C++ String类写时拷贝

    维基百科:     写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略.其核心思想是,如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变.这过程对其他的调用者都是透明的(transparently).此作法主要的优点是如果调用者没有修

string类的写时拷贝

由于浅拷贝使多个对象共用一块内存地址,调用析构函数时导致一块内存被多次释放,导致程序奔溃. 实现string类的时候通常显示的定义拷贝构造函数和运算符重载函数. 由于释放内存空间,开辟内存空间时花费时间,因此,在我们在不需要写,只是读的时候就可以不用新开辟内存空间,就用浅拷贝的方式创建对象,当我们需要写的时候才去新开辟内存空间.这种方法就是写时拷贝. 在构造函数中开辟新的空间时多开辟4个字节的空间,用来存放引用计数器,记录这快空间的引用次数. [cpp] view plain copy #inc

深拷贝&amp;浅拷贝&amp;引用计数&amp;写时拷贝

(1).浅拷贝: class String { public: String(const char* str="") :_str(new char[strlen(str)+1]) { strcpy(_str,str); } ~String() { if(NULL!=_str) { delete[] _str; _str=NULL; } } private: char* _str; }; int main() { String s1("hello"); String

Linux写时拷贝技术(copy-on-write)

1.传统的fork()函数创建一个子进程,子进程和父进程共享正文段,复制数据段,堆,栈到子进程示意图如下: 2.Linux的fork()函数-写时复制(copy-on-write)创建一个子进程,内核只为子进程创建虚拟空间,不分配物理内存,和父进程共享物理空间,当父进程中有更改相应段的行为发生时,才为子进程分配物理空间.示意图如下: 3.vfork()函数创建一个子进程,共享父进程的一切.示意图如下: 4.传统fork与copy-on-write区别 传统的fork函数直接把所有资源复制给新的进