Last modified on 13 May 2014, at 08:32

More C++ Idioms/Copy-on-write

Copy-on-writeEdit

IntentEdit

Achieve lazy copy optimization. Like lazy initialization, do the work just when you need because of efficiency.

Also Known AsEdit

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

MotivationEdit

Copying an object can sometimes cause a performance penalty. If objects are frequently copied but infrequently modified later, copy-on-write can provide significant optimization. To implement copy-on-write, a smart pointer to the real content is used to encapsulate the object's value, and on each modification an object reference count is checked; if the object is referenced more than once, a copy of the content is created before modification.

Solution and Sample CodeEdit

#ifndef COWPTR_HPP
#define COWPTR_HPP
 
#include <boost/shared_ptr.hpp>
 
template <class T>
class CowPtr
{
    public:
        typedef boost::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)
        {}
        CowPtr(const CowPtr& cowptr)
            :   m_sp(cowptr.m_sp)
        {}
        CowPtr& operator=(const CowPtr& rhs)
        {
            m_sp = rhs.m_sp; // no need to check for self-assignment with boost::shared_ptr
            return *this;
        }
        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

This implementation of copy-on-write is generic, but apart from the inconvenience of having to refer to the inner object through smart pointer dereferencing, it suffers from at least one drawback: classes that return references to their internal state, like

char & String::operator[](int)

can lead to unexpected behaviour.[1]

Consider the following code snippet

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

The intention of the last line is to modify the original string s1, not the copy, but as a side effect s2 is also accidentally modified.

A better approach is to write a custom copy-on-write implementation which is encapsulated in the class we want to lazy-copy, transparently to the user. In order to fix the problem above, one can flag objects that have given away references to inner state as "unshareable"β€”in other words, force copy operations to deep-copy the object. As an optimisation, one can revert the object to "shareable" after any non-const operations that do not give away references to inner state (for example, void string::clear()), because client code expects such references to be invalidated anyway.[1]

Known UsesEdit

Related IdiomsEdit

ReferencesEdit

  1. ↑ a b Herb Sutter, More Exceptional C++, Addison-Wesley 2002 - Items 13–16