(C++) Comparing shared\_ptrs

February 24, 2017 · View on GitHub

 

 

 

 

 

(C++) Comparing shared_ptrs

 

This article describes an architectural problem and then compares two solutions, using the C++98 and C++11 standards respectively.

 

 

 

 

 

 

The problem

 

Suppose you have a class called 'Test' you want to have managed. So, a class called 'Source' is written for simply this purpose. It is kind of a source, because it produces read-only versions of the Test managed by it. These read-only Tests are used by Observer.

 

An example where one would want to do this, is when a program loads its parameters from file. These parameters are not to be altered, but might be used throughout the program.

 

But the problem is as follows: what if the original source changes the thing its manages? How can the observer know this?

 

The code below compiles, runs but does not meet our needs:

 


#include <cassert> #include <boost/shared_ptr.hpp> struct Test {   Test(const int x = 0) : m_x(x) {}   int m_x; }; struct Source {   Source() : m_test(new Test(1)) {}   ///Note the added constness   boost::shared_ptr<const Test> Get() const { return m_test; }   void SetTest(const boost::shared_ptr<Test>& test) { m_test = test; }   private:   boost::shared_ptr<Test> m_test; }; struct Observer {   boost::shared_ptr<const Test> Get() const { return m_test; }   void SetTest(const boost::shared_ptr<const Test>& test) { m_test = test; }   private:   boost::shared_ptr<const Test> m_test; }; int main() {   Source source;   Observer observer;   observer.SetTest(source.Get());   assert(source.Get()->m_x == 1);   assert(observer.Get()->m_x == 1);   ///Put another Test in the Source, makes Observer observing an older Test   source.SetTest(boost::shared_ptr<Test>(new Test(2)));   assert(source.Get()->m_x == 2);   assert(observer.Get()->m_x == 2); //Observer still has old version }

 

The final line of code shows the problem: the observer still has an old copy of Test (with a value of one), instead of the new one (with a value of two).

 

 

 

 

 

C++98 Solution #1: use of the C++98 boost::shared_ptr

 

When instead of the observer having a boost::shared_ptr<const Test>, this is changed to boost::weak_ptr<const Test>, the program will give a fine run-time error:

 


#include <cassert> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> struct Test {   Test(const int x = 0) : m_x(x) {}   int m_x; }; struct Source {   Source() : m_test(new Test(1)) {}   ///Note the added constness   boost::shared_ptr<const Test> Get() const { return m_test; }   void SetTest(const boost::shared_ptr<Test>& test) { m_test = test; }   private:   boost::shared_ptr<Test> m_test; }; struct Observer {   boost::weak_ptr<const Test> Get() const { return m_test; }   ///Change: use of boost::weak_ptr   void SetTest(const boost::weak_ptr<const Test>& test) { m_test = test; }   private:   ///Change: use of boost::weak_ptr   boost::weak_ptr<const Test> m_test; }; int main() {   Source source;   Observer observer;   observer.SetTest(source.Get());   assert(source.Get()->m_x == 1);   assert(observer.Get().lock()->m_x == 1);   ///Put another Test in the Source, makes Observer observing an older Test   source.SetTest(boost::shared_ptr<Test>(new Test(2)));   assert(source.Get()->m_x == 2);   assert(observer.Get().lock()->m_x == 2); //Good: run-time error: 'Assertion 'px != 0' failed'. }

 

Sure, a run-time error has its drawbacks, but with a debugger its source can be located easily.

 

 

 

 

 

C++11 Solution #2: use of the C++11 std::shared_ptr

 

When replacing boost::shared_ptr and boost::weak_ptr to std::shared_ptr and std::weak_ptr respectively also gives a fine run-time error:

 


///The solution: std::weak_ptr #include <memory> #include <cassert> struct Test {   Test(const int x = 0) : m_x(x) {}   int m_x; }; struct Source {   Source() : m_test(new Test(1)) {}   ///Note the added constness   std::shared_ptr<const Test> Get() const { return m_test; }   void SetTest(const std::shared_ptr<Test>& test) { m_test = test; }   private:   std::shared_ptr<Test> m_test; }; struct Observer {   std::weak_ptr<const Test> Get() const { return m_test; }   ///Change: use of boost::weak_ptr   void SetTest(const std::weak_ptr<const Test>& test) { m_test = test; }   private:   ///Change: use of boost::weak_ptr   std::weak_ptr<const Test> m_test; }; int main() {   Source source;   Observer observer;   observer.SetTest(source.Get());   assert(source.Get()->m_x == 1);   assert(observer.Get().lock()->m_x == 1);   ///Put another Test in the Source, makes Observer observing an older Test   source.SetTest(std::shared_ptr<Test>(new Test(2)));   assert(source.Get()->m_x == 2);   assert(observer.Get().lock()->m_x == 2); //Mediocre: segmentation fault, but no reason is given }

 

The problem is, that in this case, a segmentation fault is given, instead of a failed assertion. Also, the debugger could not get me to pinpoint to the source of the error.

 

 

 

 

 

Conclusion

 

  • weak_ptr's are better observers than std::shared_ptr's
  • you might want to use boost::shared_ptr instead of std::shared_ptr until debug support is better