0
votes

I'm tyring to understand how COW works, I found following class on wikibooks, but I don't understand this code.

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->();
        }
};

And I would use it in my multithreaded application on map object, which is shared.

map<unsigned int, LPOBJECT> map;

So I've assigned it to template and now I have :

CowPtr<map<unsigned int, LPOBJECT>> map;

And now my questions :

  1. How I should propertly take instance of the map for random thread which want only read map objects ?

  2. How I should modify map object from random thread, for ex. insert new object or erase it ?

2

2 Answers

4
votes

The code you post is poor to the point of being unusable; the author doesn't seem to understand how const works in C++.

Practically speaking: CoW requires some knowledge of the operations being done on the class. The CoW wrapper has to trigger the copy when an operation on the wrapped object might modify; in cases where the wrapped object can "leak" pointers or iterators which allow modification, it also has to be able to memorize this, to require deep copy once anything has been leaked. The code you posted triggers the copy depending on whether the pointer is const or not, which isn't at all the same thing. Thus, with an std::map, calling std::map<>::find on the map should not trigger copy on write, even if the pointer is not const, and calling std::map<>::insert should, even if the pointer is const.

With regards to threading: it is very difficult to make a CoW class thread safe without grabbing a lock for every operation which may mutate, because it's very difficult to know when the actual objects are shared between threads. And it's even more difficult if the object allows pointers or iterators to leak, as do the standard library objects.

You don't explain why you want a thread-safe CoW map. What's the point of the map if each time you add or remove an element, you end up with a new copy, which isn't visible in other instances? If it's just to start individual instances with a copy of some existing map, std::map has a copy constructor which does the job just fine, and you don't need any fancy wrapper.

1
votes

How does this work?

The class class CowPtr does hold a shared pointer to the underlying object. It does have a private method to copy construct a new object and assign the pointer to to the local shared pointer (if any other object does hold a reference to it): void detach().

The relevant part of this code is, that it has each method as

const return_type&
method_name() const

and once without const. The const after a method guarantees that the method does not modify the object, the method is called a const method. As the reference to the underlying object is const too, that method is being called every time you require a reference without modifying it.

If however you chose to modify the Object behind the reference, for example:

CowPtr<std::map<unsigned int, LPOBJECT>> map;
map->clear();

the non-const method T& operator->() is being called, which calls detach(). By doing so, a copy is made if any other CowPtr or shared_ptr is referencing the same underlying object (the instance of <unsigned int, LPOBJECT> in this case)

How to use it?

Just how you would use a std::shared_ptr or boost::shared_ptr. The cool thing about that implementation is that it does everything automatically.

Remarks

This is no COW though, as a copy is made even if you do not write, it is more a Copy if you do not guarantee that you do not write-Implementation.