More C++ Idioms/Copy-on-write

From Wikibooks, open books for an open world
Jump to: navigation, search

Copy-on-write[edit]

Intent[edit]

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

Also Known As[edit]

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

Motivation[edit]

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 Code[edit]

#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 Uses[edit]

Related Idioms[edit]

References[edit]

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