|
||||
|
Section 16:
|
[16.24] How do I provide reference counting with copy-on-write semantics for a hierarchy of classes?
The previous FAQ presented a reference counting scheme that provided users with reference semantics, but did so for a single class rather than for a hierarchy of classes. This FAQ extends the previous technique to allow for a hierarchy of classes. The basic difference is that Fred::Data is now the root of a hierarchy of classes, which probably cause it to have some virtual functions. Note that class Fred itself will still not have any virtual functions. The Virtual Constructor Idiom is used to make copies of the Fred::Data objects. To select which derived class to create, the sample code below uses the Named Constructor Idiom, but other techniques are possible (a switch statement in the constructor, etc). The sample code assumes two derived classes: Der1 and Der2. Methods in the derived classes are unaware of the reference counting.
class Fred {
public:
static Fred create1(std::string const& s, int i);
static Fred create2(float x, float y);
Fred(Fred const& f);
Fred& operator= (Fred const& f);
~Fred();
void sampleInspectorMethod() const; // No changes to this object
void sampleMutatorMethod(); // Change this object
...
private:
class Data {
public:
Data() : count_(1) { }
Data(Data const& d) : count_(1) { } // Do NOT copy the 'count_' member!
Data& operator= (Data const&) { return *this; } // Do NOT copy the 'count_' member!
virtual ~Data() { assert(count_ == 0); } // A virtual destructor
virtual Data* clone() const = 0; // A virtual constructor
virtual void sampleInspectorMethod() const = 0; // A pure virtual function
virtual void sampleMutatorMethod() = 0;
private:
unsigned count_; // count_ doesn't need to be protected
friend class Fred; // Allow Fred to access count_
};
class Der1 : public Data {
public:
Der1(std::string const& s, int i);
virtual void sampleInspectorMethod() const;
virtual void sampleMutatorMethod();
virtual Data* clone() const;
...
};
class Der2 : public Data {
public:
Der2(float x, float y);
virtual void sampleInspectorMethod() const;
virtual void sampleMutatorMethod();
virtual Data* clone() const;
...
};
Fred(Data* data);
// Creates a Fred smart-reference that owns *data
// It is private to force users to use a createXXX() method
// Requirement: data must not be NULL
Data* data_; // Invariant: data_ is never NULL
};
Fred::Fred(Data* data) : data_(data) { assert(data != NULL); }
Fred Fred::create1(std::string const& s, int i) { return Fred(new Der1(s, i)); }
Fred Fred::create2(float x, float y) { return Fred(new Der2(x, y)); }
Fred::Data* Fred::Der1::clone() const { return new Der1(*this); }
Fred::Data* Fred::Der2::clone() const { return new Der2(*this); }
Fred::Fred(Fred const& f)
: data_(f.data_)
{
++data_->count_;
}
Fred& Fred::operator= (Fred const& f)
{
// DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
// (This order properly handles self-assignment)
// (This order also properly handles recursion, e.g., if a Fred::Data contains Freds)
Data* const old = data_;
data_ = f.data_;
++data_->count_;
if (--old->count_ == 0) delete old;
return *this;
}
Fred::~Fred()
{
if (--data_->count_ == 0) delete data_;
}
void Fred::sampleInspectorMethod() const
{
// This method promises ("const") not to change anything in *data_
// Therefore we simply "pass the method through" to *data_:
data_->sampleInspectorMethod();
}
void Fred::sampleMutatorMethod()
{
// This method might need to change things in *data_
// Thus it first checks if this is the only pointer to *data_
if (data_->count_ > 1) {
Data* d = data_->clone(); // The Virtual Constructor Idiom
--data_->count_;
data_ = d;
}
assert(data_->count_ == 1);
// Now we "pass the method through" to *data_:
data_->sampleMutatorMethod();
}
Naturally the constructors and sampleXXX methods for Fred::Der1
and Fred::Der2 will need to be implemented in whatever way is
appropriate.
|
|||