Reference Counting in C ++ OO-Style

I came up with an intriguing implementation of the base class in frequently asked C ++ questions , which, according to my naive understanding, can serve as an alternative to some of the smart pointer implementations (e.g. shared_ptr). Here is the sample code verbatim, but please follow the link above for an explanation:

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, eg, 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(); } 

I do not see this approach used in any C ++ libraries; although it seems pretty elegant. Assuming a single-threaded environment, for simplicity, answer the following questions:

  • Is this a suitable alternative to the smart pointer approach for managing the lifetime of objects or is it just asking for problems?
  • If this works, why do you think it is not used more often?
+8
c ++ reference-counting
source share
4 answers

Is this a suitable alternative to the smart pointer approach for managing the lifetime of objects or is it just asking for problems?

No, I donโ€™t think itโ€™s a good idea to rethink reference counting, because now we have std :: shared_ptr in C ++ 11. You can easily implement your apparently polymorphic reference class Pimpl idioms in terms of std :: shared_ptr. Please note that we no longer need to copy ctor, assignment, dtor and mutation becomes easier wrt checkout counter and cloning:

 // to be placed into a header file ... #include <memory> #include <utility> #include <string> class Fred { public: static Fred create1(std::string const& s, int i); static Fred create2(float x, float y); void sampleInspectorMethod() const; // No changes to this object void sampleMutatorMethod(); // Change this object private: class Data; std::shared_ptr<Data> data_; explicit Fred(std::shared_ptr<Data> d) : data_(std::move(d)) {} }; 

... and implementation ...

 // to be placed in the corresponding CPP file ... #include <cassert> #include "Fred.hpp" using std::shared_ptr; class Fred::Data { public: virtual ~Data() {} // A virtual destructor virtual shared_ptr<Data> clone() const = 0; // A virtual constructor virtual void sampleInspectorMethod() const = 0; // A pure virtual function virtual void sampleMutatorMethod() = 0; }; namespace { class Der1 : public Fred::Data { public: Der1(std::string const& s, int i); virtual void sampleInspectorMethod() const; virtual void sampleMutatorMethod(); virtual shared_ptr<Data> clone() const; ... }; // insert Der1 function definitions here class Der2 : public Data { public: Der2(float x, float y); virtual void sampleInspectorMethod() const; virtual void sampleMutatorMethod(); virtual shared_ptr<Data> clone() const; ... }; // insert Der2 function definitions here } // unnamed namespace Fred Fred::create1(std::string const& s, int i) { return Fred(std::make_shared<Der1>(s,i)); } Fred Fred::create2(float x, float y) { return Fred(std::make_shared<Der2>(x,y)); } 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_.unique()) data_ = data_->clone(); assert(data_.unique()); // Now we "pass the method through" to *data_: data_->sampleMutatorMethod(); } 

(unverified)

If this works, why do you think it is not used more often?

I think link counting, if you implement it yourself, is easier to make a mistake. It also has a reputation for being slow in multi-threaded environments, as link counts must increase and decrease atomically. But I guess that thanks to C ++ 11, which offers shared_ptr and moves semantics, this copy-to-write pattern may again become more popular. If you enable move semantics for the Fred class, you can avoid some of the cost of atomically increasing reference counts. Therefore, moving a Fred object from one place to another should be even faster than copying.

+4
source share

Is this a suitable alternative to the smart pointer approach for managing the lifetime of objects or is it just asking for problems?

This is an alternative, but if you have no good reason to use it, it just invents the wheel (in an untranslatable way).

If you change your code to use shared_ptr instead, you eliminate the need to explicitly define copy / ownership semantics (and define the constructor and purpose of the copy in the pimpl database). You will also use code that is already defined and tested (as it is part of the library).

If this works, why do you think it is not used more often?

Since shared_ptr is available and already implements the functionality and all the "received" ones.

+4
source share

I am also wondering if this is suitable as an alternative to a smart pointer.

But, IMO, to be a smart pointer, a class must be used as a pointer, i.e.

 SmartPtr<int> ptr = new int(42); int x = *ptr; 

So yes, this is a kind of memory management, but it is not a smart pointer, because it does not have pointer semantics.

As mentioned in the comments, the pimpl idiom is really useful for maintaining compatibility, and it can also improve development efficiency, since you don't need to recompile the class containing the class. BUT, in order to have the last advantage, you do not have to define the inner class (i.e. Data) inside the parent class, but just simply put the declaration in front and put the actual definition inside another header.

 class Fred { ... private: class Data; }; 

And I believe that for future development it is not useful to declare a data variant inside the Fred class, because if you need to add another class, you will need to modify Fred, and not just create another class. This may be required, but I suggest you avoid this part.

If I didnโ€™t understand anything, feel free to ask questions!

+3
source share
  • The C ++ answer often seems like a simpler example of how to manage shared data (using copy when writing). There are several aspects that may be important.

  • N / A with my opinion for 1.

To avoid the overhead associated with "external" link counting, as with std::shared_ptr , you can use the intrusive recount mechanism as described in Andrei Alexandrescu> book Modern C ++ Design . The Loki :: COMRefCounted class shows how to implement this ownership policy for common Windows COM objects.

Essentially, it comes down to the smart pointer template class, which takes an interface that controls the reference count and checks for the presence of delete in the instance of the pointee class itself. I do not know if the C ++ STD library is supported to implement such a policy override for the std::shared_ptr class.

We use the Loki Library exclusively for the smart pointer model in a number of embedded projects. Especially because of this ability to model the subtle granular aspects of efficiency.

Note that the proposed (built-in) implementations are not thread safe by default.

If all of the above aspects are not relevant to your goal, I would suggest switching to a simple std::shared_ptr view of your Fred::Data class, as shown in the sellibitze answer. I also agree with the points that he makes up in the last paragraph, the reference counting and the semantics of smart pointers is the tendency for him to misunderstand and implement incorrectly.

If the C ++ 11 or boost standard is not suitable for you, the loki library still provides easy integration and a robust implementation of smart pointer.

+3
source share

All Articles