FAQ 31.10 Is reference counting with copy-on-write semantics hard to implement?

graphics/new_icon.gif

It's a bit involved, but it's manageable.

Copy-on-write semantics allows users to think they're copying Fred objects, but in reality the underlying implementation doesn't actually do any copying unless and until some user actually tries to modify the copied Fred object. This approach provides users with reference semantics; the previous FAQ used reference counting to provide users with pointer semantics.

Nested class Fred::Data houses all the data that would normally go into a Fred object. Fred::Data also has an extra data member, count_, to manage the reference counting. Class Fred ends up being a smart reference: internally it points to the Fred::Data, but externally it acts as if it has the Fred::Data data within itself.

 #include <new> #include <cassert> #include <stdexcept> using namespace std; class Fred { public:   Fred() throw();                       // Default constructor   Fred(int i, int j) throw();           // Normal constructor   Fred(const Fred& f) throw();   Fred& operator= (const Fred& f) throw();  ~Fred() throw();   void sampleInspectorMethod() const throw();   void sampleMutatorMethod() throw(bad_alloc);   // ... private:   class Data {   public:     Data() throw();     Data(int i, int j) throw();     Data(const Data& d) throw();     // Since only Fred can access Fred::Data,     // the Fred::Data members can be public:.     // If that feels uncomfortable, the members can be made     // private: and class Fred can be made a friend class     // (see FAQ 19-02)     // ...     unsigned count_;     // count_ is the number of Fred objects that point at this     // count_ must be initialized to 1 by all constructors     // (starts at 1 because of the Fred object that created the this)   };   Data* data_; }; Fred::Data::Data() throw()              : count_(1) /*...*/ { } Fred::Data::Data(int i, int j) throw()  : count_(1) /*...*/ { } Fred::Data::Data(const Data& d) throw() : count_(1) /*...*/ { } Fred::Fred() throw()              : data_(new Data()) { } Fred::Fred(int i, int j) throw()  : data_(new Data(i, j)) { } Fred::Fred(const Fred& f) throw() : data_(f.data_) { ++data_->count_; } Fred& Fred::operator= (const Fred& f) throw() {   // DO NOT CHANGE THE ORDER OF THESE STATEMENTS!   // (This order properly handles self-assignment; see FAQ 24.02)   ++f.data_->count_;   if (--data_->count_ == 0) delete data_;   data_ = f.data_;   return *this; } Fred::~Fred() throw() { if (--data_->count_ == 0) delete data_; } void Fred::sampleInspectorMethod() const throw() {   // This member function promises not to change anything in *data_   // Other than that, any data access would simply use "data_->..." } void Fred::sampleMutatorMethod() throw(bad_alloc) {   // This member function 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 member function proceeds to access "data_->..." as normal } 

If it is fairly common to call Fred's default constructor, those new calls can be avoided by sharing a common Fred::Data object for all Freds that are constructed via Fred::Fred(). To avoid static initialization order problems (see FAQ 2.10), this shared Fred::Data object is created "on first use" inside a function. Here are the changes that need to be made (note that the shared Fred::Data object's destructor is never invoked; if that is a problem, either hope that there are no static initialization order problems, drop back to the approach described above, or use the nifty counter idiom (see FAQ 16.17)).

 class Fred { public:   // ... private:   // ...   static Data* defaultData() throw(); }; Fred::Fred() throw() : data_(defaultData()) { ++ data_->count_; } Fred::Data* Fred::defaultData() throw(bad_alloc) {   static Data* p = NULL;   if (p == NULL) {     p = new Data();     ++ p->count_;   // Make sure it never goes to zero   }   return p; } 

The point of all this is that users can freely copy Fred objects, but the actual data isn't copied unless and until a copy is actually needed. This can help improve performance in some cases.

To provide reference counting for a hierarchy of classes, see FAQ 31.11.



C++ FAQs
C Programming FAQs: Frequently Asked Questions
ISBN: 0201845199
EAN: 2147483647
Year: 2005
Pages: 566
Authors: Steve Summit

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net