FAQ 31.11 How can reference counting be implemented with copy-on-write semantics for a hierarchy of classes?

graphics/new_icon.gif

Through an extension of the technique for a single class.

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 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 means that it has some virtual functions. Note that class Fred itself still does not have any virtual functions.

The virtual constructor idiom (FAQ 21.07), is used to make copies of the Fred::Data objects. To select which derived class to create, the sample code uses the named constructor idiom (FAQ 16.08), but other techniques are possible (a switch statement in the constructor, for example). The sample code assumes two derived classes, Der1 and Der2. Member functions in the derived classes are unaware of the reference counting.

 #include <cassert> #include <string> using namespace std; class Fred { public:   static Fred create1(string s, int i); // Named ctors (see FAQ 16.08)   static Fred create2(float x, float y);   Fred(const Fred& f);   Fred& operator= (const Fred& f);  ~Fred();   void sampleInspectorMethod() const;   // Does not change this   void sampleMutatorMethod();           // Changes this object private:   class Data {   public:     Data() : count_(1) { }     Data(const Data& d) : count_(1) { }     Data& operator= (const Data&) { return *this; }     virtual ~Data() { assert(count_ == 0); }         // Virtual dtor     virtual Data* clone() const = 0;                 // Virtual  ctor     virtual void sampleInspectorMethod() const = 0;  // See FAQ 21.11     virtual void sampleMutatorMethod() = 0;   private:     unsigned count_;   // count_ doesn't need to be protected:     friend Fred;   };   class Der1 : public Data {   public:     Der1(string 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 createXXX()   // Requirement: data must not be NULL   Data* data_;   // Invariant: data_ is never NULL   friend Der1;   friend Der2; }; Fred::Fred(Data* data)   : data_(data)    { assert(data != NULL); } Fred::Fred(const Fred& f)   : data_(f.data_) { ++ data_->count_; } Fred Fred::create1(string 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::operator= (const Fred& f) {   // 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() { if (--data_->count_ == 0) delete data_; } void Fred::sampleInspectorMethod() const {   // This member function promises not to change anything in *data_   // Therefore we simply "pass the member function through" to *data_:   data_->sampleInspectorMethod(); } void Fred::sampleMutatorMethod() {   // 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 = data_->clone();   // Virtual ctor idiom (see FAQ 21.07)     -- data_->count_;     data_ = d;   }   assert(data_->count_ == 1);   // Now we "pass the member function through" to *data_:   data_->sampleMutatorMethod(); } 

Naturally the constructors and sampleXXX member functions for Fred::Der1 and Fred::Der2 should be implemented in whatever way is appropriate. The point is that users can copy Fred objects (pass them by value, assign them, and so on) even though they really represent a hierarchy of objects, yet the underlying data isn't actually copied unless and until a copy object is changed that is, unless and until the copy is necessary to maintain the desired observable semantics. This can improve performance in some situations.



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