I hate all these hyperintelligent email tools,
which are using all their intelligence to mess up simple text....
copy-on-write++
My code was very slow. Despite the fact that I was already using some
copy-on-write mechanism to avoid calling the copy constructor. It
involved passing a map to every child of a tree. Every child passed it
on to its children. Every child returned a map, which in turn was then
passed to the next child (its next sibling). And every child stored
the map or some changed version of it. Some elements of the tree
merged the maps returned by their children. I was using the following
classes:
ï‚· CReferenceCounted Essentially containing an integer and a method to
increment this integer (Addref()) and a method to decrement this
integer (Release()). In case of Release() caused the integer to drop
to zero, it called delete this; Of course the destructor was virtual.
ï‚· CSmartPtr Containing a pointer to some class derived from
CReferenceCounted. The constructor called AddRef() on the pointer and
the destructor called Release(). Of course there were copy constructor
and assignment operator.
ï‚· CSmartMap In essence a CSmartPtr to a class derived from
CReferenceCounted containing a std::map. The copy constructor copied
only the pointer. Then there are all the needed methods from std::map
in essence forwarding to the std::map pointed to. Any non-const method
checked the reference count and if it was larger than one, performed a
deep copy before executing the method on the std::map.
ï‚· CTreeElement An element of the tree. Every such object contains a
vector of CTreeElement representing the children. And there is the
method to pass and return the map: CSmartMap
CTreeElement ::setMap(const CSmartMap &); As I said, this method calls
every child and passes some potentially modified map:
for (std::size_t i = 0, iMax = m_sChildren.size(); i < iMax; ++i)
sMap = m_sChildren.setMap(sMap);
I was thinking, that the CSmartMap objects stayed alive too long
causing unnecessary copies to be performed. Consider the case of
passing a map to a child by calling setMap(). It would be nice if the
caller would be able to transfer ownership to the child method
setMap(), if he does not have any use anymore for the object. So this
passing of ownership must be optional. But how do you release an
object without actually destroying it? Here comes the solution:
ï‚· CCopyOnWriteCounter Similar to CReferenceCounted. The only
difference was that Release() did not destroy the object.
ï‚· CCopyOnWritePtr Derived from CSmartPtr. It must point to an object
derived from both, CReferenceCounted and CCopyOnWriteCounter. The
constructor calls the AddRef() of CCopyOnWriteCounter and the
destructor calls the Release() (remember that this Release() does not
call delete). The base class constructor and destructor of CSmartPtr
are calling into CReferenceCounted. There is of course also an
assignment operator (which in turn called the assignment operator of
the base class CSmartPtr). Since the object pointed to has two
reference counts, we can use one reference count for live time
management and one for managing copy-on-write.
I made CSmartMap derived from CCopyOnWritePtr. And I created
CSmartMap::CPointer which was a plain CSmartPtr. CSmartMap has a cast
operator to convert to CSmartMap::CPointer and a constructor with
argument type CSmartMap::CPointer. Thus if one wanted to perform some
operation on the map, one could hold a copy-on-write reference count,
since it required a CSmartMap. CSmartMap::CPointer did not provide any
functionality. The signature of the method CTreeElement::setMap() was
changed:
CSmartMap::CPointer CTreeElement ::setMap(const CSmartMap::CPointer
&);
Now every implementation of setMap() would have to watch, if it needs
some CSmartMap object again or if it does not care about it anymore:
CSmartMap::CPointer CTreeElement ::setMap(const CSmartMap::CPointer
&p)
{ m_sChildren[0].setMap(CSmartMap(p)); // need the map pointed to by p
to stay unchanged
// thus increment the copy-on-write reference count
// before calling setMap() and decrementing it after
return m_sChildren[1].setMap(p);// don’t need the map pointed to byp
after this anymore
}
Is this sufficiently clear?
Peter Foelsche
Milpitas, California, USA
Friday, April 08, 2011