Shared pointers and pointers to members issue (with fix)

Howdy everyone. I have some code that uses pointers to member variables that I want to switch to using boost::shared_ptrs, some of the code looks like this. class Bar {}; class Foo { public: Bar bar; }; ... Foo * fooptr = new Foo; Bar * barptr = &Foo.bar; ... So to make this work with shared_ptrs I can do this: class Bar {} class Foo { public: Foo() : bar(new Bar) {} shared_ptr<Bar> bar; }; ... shared_ptr<Foo> fooptr(new Foo); shared_ptr<Bar> barptr = fooptr->bar; ... But this means I have to change each of the classes... which is a bit ugly. Also it has unlinked the lifetime of the Bar and Foo objects. So the solution I've come up with means that the Foo object only gets destroyed when both the barptr and the fooptr are destroyed. Unfortunately this requires additions to the shared_ptr class (the patch is below). The changes are _very_ similar to what is used for handling the pointer conversions and casts. Anyway I was wondering whether this is useful to anyone else, and whether there was any pitfalls I might have missed.... Thanks, Mike Anderson Example Usage : #include <iostream> #include "boost/shared_ptr.hpp" class Foo { public: double m; Foo() : m(1.0) { std::cout<<"creating Foo @ "<<this<<std::endl; } ~Foo() { std::cout<<"destroying Foo @ "<<this<<std::endl; } }; int main() { { boost::shared_ptr<Foo> fooptr( new Foo ); { boost::shared_ptr<double> mptr = boost::member_pointer(fooptr, &fooptr->m); } // Foo destroyed here... } { boost::shared_ptr<double> mptr; { boost::shared_ptr<Foo> fooptr( new Foo ); mptr = boost::member_pointer(fooptr, &fooptr->m); } // Foo destroyed here... even though mptr is a pointer to double. } } class Bar {}; class Foo { public: Bar bar; }; shared_ptr<Foo> fooptr(new Foo); shared_ptr<Bar> barptr = boost::member_ptr(fooptr, &fooptr->bar); Patch : (not from the recent trunk of boost....) --- shared_ptr.hpp +++ shared_ptr.hpp @@ -50,6 +50,7 @@ struct const_cast_tag {}; struct dynamic_cast_tag {}; struct polymorphic_cast_tag {}; +struct memberpointer_tag {}; template<class T> struct shared_ptr_traits { @@ -191,6 +192,12 @@ } } + template<class Y, class M> + shared_ptr(shared_ptr<Y> const & r, M* member_ptr, detail::memberpointer_tag) : px( member_ptr ), pn(r.pn) + { + + } + #ifndef BOOST_NO_AUTO_PTR template<class Y> @@ -410,6 +417,11 @@ return shared_static_cast<T>(r); } +template<class T, class M> shared_ptr<M> member_pointer( shared_ptr<T> const & t, M* m) +{ + return shared_ptr<M>(t,m, detail::memberpointer_tag() ); +} + // get_pointer() enables boost::mem_fn to recognize shared_ptr template<class T> inline T * get_pointer(shared_ptr<T> const & p) -- Michael Anderson Chief Technology Officer -- p. +61 8 8400 6496 [Adelaide, Australia] michael.anderson@risingsunresearch.com -- Rising Sun Research p. +61 8 8400 6494 - f. +61 8 8400 6401 www.risingsunresearch.com

Michael Anderson wrote:
Howdy everyone. I have some code that uses pointers to member variables that I want to switch to using boost::shared_ptrs, some of the code looks like this.
class Bar {}; class Foo { public: Bar bar; };
... Foo * fooptr = new Foo; Bar * barptr = &Foo.bar; ...
So to make this work with shared_ptrs I can do this:
class Bar {} class Foo { public: Foo() : bar(new Bar) {} shared_ptr<Bar> bar; };
... <snip>
Pardon my ignorance but why not just use scoped_ptr<Bar>? - Michael Marcin

Michael Marcin wrote:
Michael Anderson wrote:
Howdy everyone. I have some code that uses pointers to member variables that I want to switch to using boost::shared_ptrs, some of the code looks like this.
class Bar {}; class Foo { public: Bar bar; };
... Foo * fooptr = new Foo; Bar * barptr = &Foo.bar; ...
So to make this work with shared_ptrs I can do this:
class Bar {} class Foo { public: Foo() : bar(new Bar) {} shared_ptr<Bar> bar; };
... <snip>
Pardon my ignorance but why not just use scoped_ptr<Bar>?
- Michael Marcin
For example I want this to work... where ptr<Bar> is some kind of smart (or otherwise) pointer to a bar object. { ptr<Bar> barPtr; { ptr<Foo> fooPtr(new Foo); barPtr = fooPtr->bar; //A } barPtr->doSomething(); //B //C } Note that fooPtr goes out of scope at line A, but if the Foo object is destroyed at A then line B is an error. Instead I want the Foo object to exist until line C where both barPtr and fooPtr go out of scope. I don't believe this will work with any combination of barPtr, the internal Foo::bar, and fooPtr being scoped pointers... If there is a way to get something equivalent to the above fragment of code to work and be correct using scoped pointers I'd love to see it. We can't use scoped pointers because there are other constraints on our code than the simplistic example shown here. But any trick that causes the above code to work for scoped pointers should equally work with shared pointers. Michael Anderson

Michael Anderson wrote: [snip]
For example I want this to work... where ptr<Bar> is some kind of smart (or otherwise) pointer to a bar object.
{ ptr<Bar> barPtr; { ptr<Foo> fooPtr(new Foo); barPtr = fooPtr->bar; //A } barPtr->doSomething(); //B //C }
Something similar is possible if Bar is polymorphic, see below. I'm leaving out includes and the "boost::" namespace references, but it should give you the basic idea: --- Bar.h --- struct Bar { virtual void Baz() = 0; }; --- Foo.h --- struct Foo : enable_shared_from_this<Foo> { Foo(); shared_ptr<Bar> GetBar(); private: shared_ptr<Bar> pBar_; }; ---- Foo.cpp ---- namespace { struct BarImpl : Bar { void Baz() { } }; struct BarDelegatorWithFooRef : Bar { BarDelegatorWithFooRef(shared_ptr<Foo> pFoo, shared_ptr<Bar> pBar) : pFoo_(pFoo) , pBar_(pBar) {} void Baz() { pBar_->Baz(); } private: shared_ptr<Foo> pFoo_; shared_ptr<Bar> pBar_; }; } // anonymous namespace Foo::Foo() : pBar_(new BarImpl) {} shared_ptr<Bar> Foo::GetBar() { return shared_ptr<Bar>(new BarDelegatorWithFooRef(shared_from_this(), pBar_)); } --------------- The following should then work as you wished: { shared_ptr<Bar> barPtr; { shared_ptr<Foo> fooPtr(new Foo); barPtr = fooPtr->GetBar(); } barPtr->doSomething(); } Beware of typos! / Johan

Michael Anderson wrote:
<snip>
For example I want this to work... where ptr<Bar> is some kind of smart (or otherwise) pointer to a bar object.
{ ptr<Bar> barPtr; { ptr<Foo> fooPtr(new Foo); barPtr = fooPtr->bar; //A } barPtr->doSomething(); //B //C }
Note that fooPtr goes out of scope at line A, but if the Foo object is destroyed at A then line B is an error. Instead I want the Foo object to exist until line C where both barPtr and fooPtr go out of scope.
<snip>
I see... This sort of usage smells bad to me but if you have solid reasons behind it perhaps this is a case for 2 shared pointers using the same reference count object as was discussed on this list a while ago. i.e. something like class Bar {}; class Foo : enable_shared_from_this<Foo> { public: Foo() : bar( shared_ptr<Bar>(new Bar, shared_from_this()) ) {} weak_ptr<Bar> bar; }; { shared_ptr<Bar> barPtr; { shared_ptr<Foo> fooPtr(new Foo); barPtr = fooPtr->bar.lock(); } //A barPtr->doSomething(); } //B where nothing gets destroyed at //A but both get destroyed at //B hmm I don't think this will quite work but I'll post it anyways to get more ideas. - Michael Marcin

Michael Anderson wrote:
Howdy everyone. I have some code that uses pointers to member variables that I want to switch to using boost::shared_ptrs, some of the code looks like this.
class Bar {}; class Foo { public: Bar bar; };
... Foo * fooptr = new Foo; Bar * barptr = &Foo.bar; ...
So to make this work with shared_ptrs I can do this:
class Bar {} class Foo { public: Foo() : bar(new Bar) {} shared_ptr<Bar> bar; };
... shared_ptr<Foo> fooptr(new Foo); shared_ptr<Bar> barptr = fooptr->bar; ...
But this means I have to change each of the classes... which is a bit ugly. Also it has unlinked the lifetime of the Bar and Foo objects.
So the solution I've come up with means that the Foo object only gets destroyed when both the barptr and the fooptr are destroyed. Unfortunately this requires additions to the shared_ptr class (the patch is below). The changes are _very_ similar to what is used for handling the pointer conversions and casts.
There has been a formal proposal to add an "aliasing constructor" (as in your patch, but public and without the tag) to std::tr1::shared_ptr and I'm considering adding one for 1.35. In the meantime,
+template<class T, class M> shared_ptr<M> member_pointer( shared_ptr<T> const & t, M* m) +{ + return shared_ptr<M>(t,m, detail::memberpointer_tag() ); +}
you can use: template<class T, class M> shared_ptr<M> member_pointer( shared_ptr<T> const & t, M* m) { return shared_ptr<M>( m, bind( &shared_ptr<T>::reset, t ) ); } A similar trick is described in: http://www.boost.org/libs/smart_ptr/sp_techniques.html#another_sp

Peter Dimov wrote:
So the solution I've come up with means that the Foo object only gets destroyed when both the barptr and the fooptr are destroyed. Unfortunately this requires additions to the shared_ptr class (the patch is below). The changes are _very_ similar to what is used for handling the pointer conversions and casts.
There has been a formal proposal to add an "aliasing constructor" (as in your patch, but public and without the tag) to std::tr1::shared_ptr and I'm considering adding one for 1.35.
That's nice to hear, as a few other people inside my company have mentioned that such a feature would be useful to them. (and we'd rather not patch our copies of the boost libraries)
In the meantime, ... snip ... you can use:
template<class T, class M> shared_ptr<M> member_pointer( shared_ptr<T> const & t, M* m) { return shared_ptr<M>( m, bind( &shared_ptr<T>::reset, t ) ); }
This seems to work perfectly. One thing to note is that this solution uses extra memory for storing an internal copy of the boost shared pointer and the custom deleter.
A similar trick is described in:
http://www.boost.org/libs/smart_ptr/sp_techniques.html#another_sp
This is nifty as it lets you get back to the parent pointer if you want, but is essentially the same as the method outlined above (not a criticism, just a note for those who dont want to follow the link) So in summary: these are both great solutions, but I feel that an aliasing constructor would be a great addition to the boost shared pointers library. Thanks, Michael Anderson

On Mar 1, 2007, at 2:09 PM, Peter Dimov wrote:
Michael Anderson wrote:
... There has been a formal proposal to add an "aliasing constructor" (as in your patch, but public and without the tag) to std::tr1::shared_ptr and I'm considering adding one for 1.35. In the meantime,
Ah ah. I didn't read all the messages before my previous reply. Thanks Peter for the update. One more question: Would aliasing also make it to tr1? Or would that proposal go into tr2? -- Herve

Hervé Brönnimann wrote:
On Mar 1, 2007, at 2:09 PM, Peter Dimov wrote:
Michael Anderson wrote:
... There has been a formal proposal to add an "aliasing constructor" (as in your patch, but public and without the tag) to std::tr1::shared_ptr and I'm considering adding one for 1.35. In the meantime,
Ah ah. I didn't read all the messages before my previous reply. Thanks Peter for the update. One more question: Would aliasing also make it to tr1? Or would that proposal go into tr2?
My current understanding is that most of TR1 has been moved to C++09, including shared_ptr, and there will probably be no updates to TR1. I'm not sure about whether TR2 would be able to affect std::shared_ptr; probably not as TRs are preferably composed of pure extensions.

Michael: This is exactly what aliasing is for. I don't use boost::shared_ptr, but just looking at it I can't find any mention of aliasing. Maybe the library authors will reply to you. Meanwhile, I believe that aliasing is much more general than sharing accesses to members. The usual examples are about pointer to a temporary buffer (e.g. for I/O) and various pointers pointing inside the buffer, or a pointer to buffers in an I/O vec chain, or iterator/node pointers inside a list, all of which must live at least as long as the buffer / chain / list to which they're members of. I know that the shared pointer at my firm supports aliasing and that is a very useful and widely used feature. I'm surprised it wouldn't be supported by boost. Cheers, -- Herve On Feb 28, 2007, at 12:22 AM, Michael Anderson wrote:
Howdy everyone. I have some code that uses pointers to member variables that I want to switch to using boost::shared_ptrs, some of the code looks like this.
class Bar {}; class Foo { public: Bar bar; };
... Foo * fooptr = new Foo; Bar * barptr = &Foo.bar; ...
So to make this work with shared_ptrs I can do this:
class Bar {} class Foo { public: Foo() : bar(new Bar) {} shared_ptr<Bar> bar; };
... shared_ptr<Foo> fooptr(new Foo); shared_ptr<Bar> barptr = fooptr->bar; ...
But this means I have to change each of the classes... which is a bit ugly. Also it has unlinked the lifetime of the Bar and Foo objects.
So the solution I've come up with means that the Foo object only gets destroyed when both the barptr and the fooptr are destroyed. Unfortunately this requires additions to the shared_ptr class (the patch is below). The changes are _very_ similar to what is used for handling the pointer conversions and casts.
Anyway I was wondering whether this is useful to anyone else, and whether there was any pitfalls I might have missed.... Thanks, Mike Anderson
Example Usage :
#include <iostream> #include "boost/shared_ptr.hpp" class Foo { public: double m; Foo() : m(1.0) { std::cout<<"creating Foo @ "<<this<<std::endl; }
~Foo() { std::cout<<"destroying Foo @ "<<this<<std::endl; } };
int main() { { boost::shared_ptr<Foo> fooptr( new Foo ); { boost::shared_ptr<double> mptr = boost::member_pointer(fooptr, &fooptr->m); } // Foo destroyed here... }
{ boost::shared_ptr<double> mptr; { boost::shared_ptr<Foo> fooptr( new Foo ); mptr = boost::member_pointer(fooptr, &fooptr->m); } // Foo destroyed here... even though mptr is a pointer to double. } }
class Bar {}; class Foo { public: Bar bar; };
shared_ptr<Foo> fooptr(new Foo); shared_ptr<Bar> barptr = boost::member_ptr(fooptr, &fooptr->bar);
Patch : (not from the recent trunk of boost....)
--- shared_ptr.hpp +++ shared_ptr.hpp @@ -50,6 +50,7 @@ struct const_cast_tag {}; struct dynamic_cast_tag {}; struct polymorphic_cast_tag {}; +struct memberpointer_tag {};
template<class T> struct shared_ptr_traits { @@ -191,6 +192,12 @@ } }
+ template<class Y, class M> + shared_ptr(shared_ptr<Y> const & r, M* member_ptr, detail::memberpointer_tag) : px( member_ptr ), pn(r.pn) + { + + } + #ifndef BOOST_NO_AUTO_PTR
template<class Y> @@ -410,6 +417,11 @@ return shared_static_cast<T>(r); }
+template<class T, class M> shared_ptr<M> member_pointer ( shared_ptr<T> const & t, M* m) +{ + return shared_ptr<M>(t,m, detail::memberpointer_tag() ); +} + // get_pointer() enables boost::mem_fn to recognize shared_ptr
template<class T> inline T * get_pointer(shared_ptr<T> const & p)
-- Michael Anderson Chief Technology Officer -- p. +61 8 8400 6496 [Adelaide, Australia] michael.anderson@risingsunresearch.com -- Rising Sun Research p. +61 8 8400 6494 - f. +61 8 8400 6401 www.risingsunresearch.com
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/ listinfo.cgi/boost

A nice discussion of the aliasing concept can be found in the paper "Improving Usability and Performance of TR1 Smart Pointers, N1851" by Kliatchko and Rocha. It can be found at: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1851.pdf I believe this is the proposed aliasing extension to TR1 smart_ptr that was mentioned earlier. Braddock Gaskill Dockside Vision Inc

Greetings, On Sunday 04 March 2007 02:40:20 Braddock Gaskill wrote:
A nice discussion of the aliasing concept can be found in the paper "Improving Usability and Performance of TR1 Smart Pointers, N1851" by Kliatchko and Rocha. It can be found at:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1851.pdf
I believe this is the proposed aliasing extension to TR1 smart_ptr that was mentioned earlier.
Reading n1851, a thought comes to mind: maybe TR1/Boost should change shared_ptr to accept an optional *allocator* instead of an optional *deleter* and fold the deleter case into the concept of allocator ? Just my $0.02, Davide Bolcioni -- There is no place like /home.

Davide Bolcioni wrote:
Greetings,
On Sunday 04 March 2007 02:40:20 Braddock Gaskill wrote:
A nice discussion of the aliasing concept can be found in the paper "Improving Usability and Performance of TR1 Smart Pointers, N1851" by Kliatchko and Rocha. It can be found at:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1851.pdf
I believe this is the proposed aliasing extension to TR1 smart_ptr that was mentioned earlier.
Reading n1851, a thought comes to mind: maybe TR1/Boost should change shared_ptr to accept an optional *allocator* instead of an optional *deleter* and fold the deleter case into the concept of allocator ?
The two concepts are distinct. The deleter is used to delete the pointer you pass to shared_ptr. The allocator is used for the internal allocation and deallocation of the control block (which holds the reference counts.) boost::shared_ptr already supports the allocator constructor, BTW; I'm not sure whether it's in 1.34, though.
participants (8)
-
Braddock Gaskill
-
Davide Bolcioni
-
Hervé Brönnimann
-
Johan Nilsson
-
Michael Anderson
-
Michael Marcin
-
Michael Marcin
-
Peter Dimov