|
||||
|
Section 16:
|
[16.23] How do I provide reference counting with copy-on-write semantics?
Reference counting can be done with either pointer semantics or reference semantics. The previous FAQ shows how to do reference counting with pointer semantics. This FAQ shows how to do reference counting with reference semantics. The basic idea is to allow users to think they're copying your Fred objects, but in reality the underlying implementation doesn't actually do any copying unless and until some user actually tries to modify the underlying Fred object. Class Fred::Data houses all the data that would normally go into the Fred class. Fred::Data also has an extra data member, count_, to manage the reference counting. Class Fred ends up being a "smart reference" that (internally) points to a Fred::Data.
class Fred {
public:
Fred(); // A default constructor
Fred(int i, int j); // A normal constructor
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();
Data(int i, int j);
Data(Data const& d);
// Since only Fred can access a Fred::Data object,
// you can make Fred::Data's data public if you want.
// But if that makes you uncomfortable, make the data private
// and make Fred a friend class via friend class Fred;
...data goes here...
unsigned count_;
// count_ is the number of Fred objects that point at this
// count_ must be initialized to 1 by all constructors
// (it starts as 1 since it is pointed to by the Fred object that created it)
};
Data* data_;
};
Fred::Data::Data() : count_(1) /*init other data*/ { }
Fred::Data::Data(int i, int j) : count_(1) /*init other data*/ { }
Fred::Data::Data(Data const& d) : count_(1) /*init other data*/ { }
Fred::Fred() : data_(new Data()) { }
Fred::Fred(int i, int j) : data_(new Data(i, j)) { }
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_
// Other than that, any data access would simply use "data_->..."
}
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 = new Data(*data_); // Invoke Fred::Data's copy ctor
--data_->count_;
data_ = d;
}
assert(data_->count_ == 1);
// Now the method proceeds to access "data_->..." as normal
}
If it is fairly common to call Fred's default
constructor, you can avoid all those new calls by sharing
a common Fred::Data object for all Freds that are constructed via
Fred::Fred(). To avoid static initialization order problems, this
shared Fred::Data object is created "on first use" inside a function.
Here are the changes that would be made to the above code (note that the shared
Fred::Data object's destructor is never invoked; if that is a problem,
either hope you don't have any static initialization order problems, or drop
back to the approach described above):
class Fred {
public:
...
private:
...
static Data* defaultData();
};
Fred::Fred()
: data_(defaultData())
{
++data_->count_;
}
Fred::Data* Fred::defaultData()
{
static Data* p = NULL;
if (p == NULL) {
p = new Data();
++p->count_; // Make sure it never goes to zero
}
return p;
}
Note: You can also provide reference
counting for a hierarchy of classes if your Fred class would normally
have been a base class.
|
|||