A copied class is required to provide just one interface:
Copied& copy()
The point of being able to copy a class instance is not simply that it can be copied; it is also that the copies of an individual instance share their content or state. This turns out to be exactly the situation when there are multiple pointers to the same (shared) memory on the heap, so there is nothing conceptually new there. What is new is that, whatever shared resouces there are, they are automatically managed and deleted when they are no longer required.
It will surprise noone that shared pointers are somewhere in this picture. But which shared pointer? We all know about the now-deprecated auto_ptr in the standard library, but the superior replacement for it, shared_ptr, has been a long time coming. For this reason, I devised my own, extremely simple, managed pointer class, Pointer. It is the backbone of the Limpid library:
template<class T>
struct PHolder {
public:
PHolder<T>(): pointer_(0), refCount_(1) {}
PHolder<T>(T* pointer): pointer_(pointer), refCount_(1) {}
T *pointer_;
unsigned int refCount_;
};
template<class T>
class Pointer {
public:
Pointer<T>(): holder_(new PHolder<T>) { }
Pointer<T>(T *pointer): holder_(new PHolder<T>(pointer)) {}
Pointer<T>(const Pointer<T>& refPointer) { copyContent(refPointer); }
~Pointer<T>() { deleteContent(); }
Pointer<T>& operator=(const Pointer<T>& refPointer) {
if (holder_ != refPointer.holder_) {
deleteContent();
copyContent(refPointer);
}
return *this;
}
T* operator->() const { return holder_->pointer_; }
T& operator*() const { return *(holder_->pointer_); }
T* getPointer() const { return holder_->pointer_; }
operator bool() const { return holder_->pointer_; }
private:
PHolder<T> *holder_;
void copyContent(const Pointer<T>& refPointer) {
holder_ = refPointer.holder_;
++holder_->refCount_;
}
void deleteContent() {
if (!--holder_->refCount_) {
delete holder_->pointer_;
delete holder_;
}
}
};
Pointer uses a helper class, PHolder, to encapsulate a reference counter and the actual pointer to be managed. Keeping them together in this way helps to limit the fragmentation of heap/free space memory. Note that there are two constuctors for Pointer: the default constructor creates a PHolder with a null pointer, while the other constuctor installs the pointer in the PHolder instance together with a reference count of 1.
Remember the copy() interface. This is a virtual function; so it supports polymorphism, but it is not an interface to be used by a typical user of a library. Instead, it is used consistently inside compound classes, and is the standard method for incorporating (composing) simple classes inside higher classes. But it is highly hazardous, and should be left to the library developers. To understand why, look at an example of copy() in action:
Writer& Writer::copy() {
return *(new Writer(*this));
}
See the danger? We are returning a reference to a Writer by creating a copy of a Writer on the heap and dereferencing a pointer to it. If we do not do the right thing about the reference, therefore, we have a memory leak. So how do we handle it? Remember that Writer is a subclass of CharacterWriter, and consider how a CharacterWriter is used to construct an XMLWriter:
XMLWriter::XMLWriter(CharacterWriter& op, bool isHTML): emptySet_(new StringSet),
writer_(&op.copy()), format_(new OutputFormat), isHTML_(isHTML) { ....
Note how a copy of the writer is incorporated into writer_ (a Pointer<CharacterWriter>). This pattern turns up right through the Limpid library and provides for very clean and consistent code. And it has another very nice consequence: any class that has copyable content is itself copyable. As a result, it is very simple to design complex hierarchies of classes that are built on several layers if simpler classes.
In alphabetical order these are:
These are all classes that embed, or are embedded in, other classes, which may in turn embed or be embedded.
There are three implications that should be recognised:
Now that we have come to this point, it is fair to point out that the W3C DOM recommendation has a node interface:
Node& cloneNode(bool deep);
When this is used to clone a node, it produces a replica of the original node, ostensibly identical to the original node, but a totally independent entity that can, for example, be appended as a child of some other node. To repeat, this is part of the W3C recommendation. But Limpid, for its own internal purposes, uses the copy interface.