[smart_ptr] enable_shared_from_this and shared_ptr to a base class
Hi,
I have a question related to using shared_from_this() and a shared_ptr
to a base class.
Here's a modified version of the enable_shared_from_this usage example
from its documentation web page:
#include
On Mon, Jun 24, 2013 at 10:28 AM, Adam Romanek
Hi,
I have a question related to using shared_from_this() and a shared_ptr to a base class.
Here's a modified version of the enable_shared_from_this usage example from its documentation web page:
#include
#include #include <cassert> class X { public: virtual void foo() = 0; };
class Y: public X, public boost::enable_shared_from_**this<Y> { virtual void foo() { shared_from_this(); } };
int main() { X* x_ptr = new Y; boost::shared_ptr<X> sp(x_ptr); sp->foo(); }
First, class Y is now derived from class X. Secondly, shared_ptr's template argument is now X and the parameter to the shared_ptr's constructor is now a pointer to X.
Now, calling shared_from_this() in X and/or Y throws boost::bad_weak_ptr.
Is this correct behavior? Is it documented somewhere?
This is expected, given the way it's implemented and described. shared_from_this() description [1] mentions that there must be at least one shared_ptr that owns the object. I didn't find any formal definition of the "owns" relation but it doesn't look to me that it applies to your example, because there is no shared_ptr pointing to Y in your code. [1] http://www.boost.org/doc/libs/release/libs/smart_ptr/enable_shared_from_this...
On 06/24/2013 08:52 AM, Andrey Semashev wrote:
On Mon, Jun 24, 2013 at 10:28 AM, Adam Romanek
wrote: class X { ... };
class Y: public X, public boost::enable_shared_from_**this<Y> { ... };
int main() { X* x_ptr = new Y; boost::shared_ptr<X> sp(x_ptr); sp->foo(); }
This is expected, given the way it's implemented and described. shared_from_this() description [1] mentions that there must be at least one shared_ptr that owns the object. I didn't find any formal definition of the "owns" relation but it doesn't look to me that it applies to your example, because there is no shared_ptr pointing to Y in your code.
[1] http://www.boost.org/doc/libs/release/libs/smart_ptr/enable_shared_from_this...
Andrey! Thanks for such a quick response. OK, this behavior may in fact be expected considering the implementation. However, I would not say the documentation reflects that. But let's look at the code first. You wrote that there is no shared_ptr pointing to Y in my code. I can't agree. There is - sp. It points to Y through a pointer to X, which is perfectly valid. Moreover, in my opinion it "owns" an instance of Y and will attempt to destroy it when necessary. Maybe it's a matter of using the right words but a colleague of mine spent an hour or two looking for a bug in his code, just to find out about this "feature" of enable_shared_from_this. I appreciate your explanation but still, I think that the documentation is a bit imprecise and could be improved so that things were more clear. WBR, Adam Romanek
On Mon, Jun 24, 2013 at 11:22 AM, Adam Romanek
On 06/24/2013 08:52 AM, Andrey Semashev wrote:
On Mon, Jun 24, 2013 at 10:28 AM, Adam Romanek
wrote:
class X { ... };
class Y: public X, public boost::enable_shared_from_****this<Y> { ...
};
int main() { X* x_ptr = new Y; boost::shared_ptr<X> sp(x_ptr); sp->foo(); }
This is expected, given the way it's implemented and described. shared_from_this() description [1] mentions that there must be at least one shared_ptr that owns the object. I didn't find any formal definition of the "owns" relation but it doesn't look to me that it applies to your example, because there is no shared_ptr pointing to Y in your code.
[1] http://www.boost.org/doc/libs/**release/libs/smart_ptr/enable_** shared_from_this.htmlhttp://www.boost.org/doc/libs/release/libs/smart_ptr/enable_shared_from_this...
Andrey!
Thanks for such a quick response.
OK, this behavior may in fact be expected considering the implementation. However, I would not say the documentation reflects that. But let's look at the code first.
You wrote that there is no shared_ptr pointing to Y in my code. I can't agree. There is - sp. It points to Y through a pointer to X, which is perfectly valid. Moreover, in my opinion it "owns" an instance of Y and will attempt to destroy it when necessary.
The shared_ptr has no knowledge of Y since it is not constructed with a pointer to Y. Actually, without a virtual destructor (like in your code) Y is never destructed. So from the pointer's standpoint, it doesn't own Y, but it owns X. I know, this may not be obvious and it deserves better documentation.
I appreciate your explanation but still, I think that the documentation is a bit imprecise and could be improved so that things were more clear.
Agreed.
On 24 June 2013 08:22, Adam Romanek wrote:
You wrote that there is no shared_ptr pointing to Y in my code. I can't agree. There is - sp. It points to Y through a pointer to X, which is perfectly valid. Moreover, in my opinion it "owns" an instance of Y and will attempt to destroy it when necessary.
No it won't, the program has undefined behaviour because X::~X() is not virtual. Don't upcast the Y* to X* before giving ownership to a shared_ptr, that's a bug in your code. Everything works if you fix that. Even if the destructor was virtual (so the code didn't have undefined behaviour) your expectation of enable_shared_from_this is wrong. For it to work would require a dynamic_cast in every shared_ptr constructor taking a raw pointer, to check if the X* points to a base class of a Y*. That would add unacceptable overhead for many people. The documentation says that there must be a shared_ptr that owns t, where t is an instance of T (in your case T is Y). That is not true for your program, for a shared_ptr to "own" a pointer t it must have been constructed with a copy of _that_ pointer, not some other pointer with a different type to some base class of the same object. The shared_ptr owns the pointer it was constructed with, and you do not construct it with a Y*, so no shared_ptr owns a pointer to your Y object. I think there is a small documentation bug though, enable_shared_from_this talks about owning an object of type T, but the shared_ptr docs talk about owning a pointer. The docs for std::enable_shared_from_this correctly say "There shall be at least one shared_ptr instance p that owns &t." (as opposed to "owns t"). The & should be added to the Boost docs.
On Mon, Jun 24, 2013 at 12:06 PM, Jonathan Wakely
I think there is a small documentation bug though, enable_shared_from_this talks about owning an object of type T, but the shared_ptr docs talk about owning a pointer. The docs for std::enable_shared_from_this correctly say "There shall be at least one shared_ptr instance p that owns &t." (as opposed to "owns t"). The & should be added to the Boost docs.
I think it is more correct to say "the pointer owns the [pointed to] object" rather than "the pointer owns the pointer to the object". If the citation you quoted is taken from the standard, then it probably should be corrected.
On 24 June 2013 09:37, Andrey Semashev wrote:
On Mon, Jun 24, 2013 at 12:06 PM, Jonathan Wakely
I think there is a small documentation bug though, enable_shared_from_this talks about owning an object of type T, but the shared_ptr docs talk about owning a pointer. The docs for std::enable_shared_from_this correctly say "There shall be at least one shared_ptr instance p that owns &t." (as opposed to "owns t"). The & should be added to the Boost docs.
I think it is more correct to say "the pointer owns the [pointed to] object" rather than "the pointer owns the pointer to the object". If the citation you quoted is taken from the standard, then it probably should be corrected.
No, please read the shared_ptr specification before deciding what should be corrected, it is very clear in the Boost docs and the standard: "constructs a shared_ptr that owns the pointer p." That is not a mistake. If I construct shared_ptr<T>((T*)0, D()) then it owns a null pointer and will invoke the deleter D on the null pointer. That has well-defined semantics and you can't do that if it has to own an object rather than a pointer.
On 06/24/2013 10:06 AM, Jonathan Wakely wrote:
On 24 June 2013 08:22, Adam Romanek wrote:
You wrote that there is no shared_ptr pointing to Y in my code. I can't agree. There is - sp. It points to Y through a pointer to X, which is perfectly valid. Moreover, in my opinion it "owns" an instance of Y and will attempt to destroy it when necessary.
No it won't, the program has undefined behaviour because X::~X() is not virtual.
Yep, my bad. But this discussion is not about destroying X / Y correctly.
Don't upcast the Y* to X* before giving ownership to a shared_ptr, that's a bug in your code. Everything works if you fix that.
Even if the destructor was virtual (so the code didn't have undefined behaviour) your expectation of enable_shared_from_this is wrong. For it to work would require a dynamic_cast in every shared_ptr constructor taking a raw pointer, to check if the X* points to a base class of a Y*. That would add unacceptable overhead for many people.
I haven't thought about implementation implications of using enable_shared_from_this the way I used it in the example code. Please note that this was just an example. The issue arose in a more complicated code where things are not so simple and easy to catch.
The documentation says that there must be a shared_ptr that owns t, where t is an instance of T (in your case T is Y). That is not true for your program, for a shared_ptr to "own" a pointer t it must have been constructed with a copy of _that_ pointer, not some other pointer with a different type to some base class of the same object. The shared_ptr owns the pointer it was constructed with, and you do not construct it with a Y*, so no shared_ptr owns a pointer to your Y object.
When taking inheritance into account one can safely say that a pointer to a base class X pointing to an instance of a derived class Y owns this instance. That's my point of view. So maybe "ownership" should be described in more detail?
I think there is a small documentation bug though, enable_shared_from_this talks about owning an object of type T, but the shared_ptr docs talk about owning a pointer. The docs for std::enable_shared_from_this correctly say "There shall be at least one shared_ptr instance p that owns &t." (as opposed to "owns t"). The & should be added to the Boost docs.
WBR, Adam Romanek
On 24 June 2013 10:22, Adam Romanek wrote:
On 06/24/2013 10:06 AM, Jonathan Wakely wrote:
The documentation says that there must be a shared_ptr that owns t, where t is an instance of T (in your case T is Y). That is not true for your program, for a shared_ptr to "own" a pointer t it must have been constructed with a copy of _that_ pointer, not some other pointer with a different type to some base class of the same object. The shared_ptr owns the pointer it was constructed with, and you do not construct it with a Y*, so no shared_ptr owns a pointer to your Y object.
When taking inheritance into account one can safely say that a pointer to a base class X pointing to an instance of a derived class Y owns this instance. That's my point of view.
No, that's not how shared_ptr's ownership works. It owns a pointer and when the last owner releases ownership the pointer is passed to the deleter (which might just call 'delete'). But there is no requirement that the pointer is non-null, or points to an instance of some specific type, and certainly no requirement that if the shared_ptr owns an X* that it has anything to do with any instance of a different type Y, even if Y is related to X by inheritance. The shared_ptr doesn't care about such things, it just owns a pointer and arranges for it to be passed to the relevant deleter at the appropriate time. In this example the four shared_ptr objects all share ownership of the same pointer of type Y*, even though one of them is a shared_ptr<X> and one is shared_ptr<void> and one stores a completely unrelated pointer type, but still owns a pointer of type Y*: struct X { virtual ~X() {} }; struct Y : X { }; std::shared_ptr<Y> sy(new Y); std::shared_ptr<X> sx(sy); std::shared_ptr<void> sv(sx); std::shared_ptr<int> si(sv, (int*)0); The ownership is a property of the static type of the pointer passed to the first shared_ptr constructor that creates a unique shared_ptr (i.e. one with use_count()==1). If you don't pass a Y* to the first constructor then it doesn't own a Y* and won't initialize any enable_shared_from_this<Y> base class.
So maybe "ownership" should be described in more detail?
Yes, if people are making up their own interpretations of it then maybe it should be.
On 06/24/2013 12:05 PM, Jonathan Wakely wrote:
On 24 June 2013 10:22, Adam Romanek wrote:
On 06/24/2013 10:06 AM, Jonathan Wakely wrote:
When taking inheritance into account one can safely say that a pointer to a base class X pointing to an instance of a derived class Y owns this instance. That's my point of view.
No, that's not how shared_ptr's ownership works. It owns a pointer and when the last owner releases ownership the pointer is passed to the deleter (which might just call 'delete'). But there is no requirement that the pointer is non-null, or points to an instance of some specific type, and certainly no requirement that if the shared_ptr owns an X* that it has anything to do with any instance of a different type Y, even if Y is related to X by inheritance. The shared_ptr doesn't care about such things, it just owns a pointer and arranges for it to be passed to the relevant deleter at the appropriate time.
I get your point. I've never expected that shared_ptr would (auto)magically "detect" any relation between X and Y or do anything that is unrelated to its nominal functionality.
In this example the four shared_ptr objects all share ownership of the same pointer of type Y*, even though one of them is a shared_ptr<X> and one is shared_ptr<void> and one stores a completely unrelated pointer type, but still owns a pointer of type Y*:
struct X { virtual ~X() {} }; struct Y : X { };
std::shared_ptr<Y> sy(new Y); std::shared_ptr<X> sx(sy); std::shared_ptr<void> sv(sx); std::shared_ptr<int> si(sv, (int*)0);
The ownership is a property of the static type of the pointer passed to the first shared_ptr constructor that creates a unique shared_ptr (i.e. one with use_count()==1). If you don't pass a Y* to the first constructor then it doesn't own a Y* and won't initialize any enable_shared_from_this<Y> base class.
That's something that is truly important in the case we're discussing. In fact, the last paragraph makes things absolutely clear. This is what I would expect to see in the docs if I was in trouble.
So maybe "ownership" should be described in more detail?
Yes, if people are making up their own interpretations of it then maybe it should be.
WBR, Adam Romanek
participants (3)
-
Adam Romanek
-
Andrey Semashev
-
Jonathan Wakely