shared_from_this() fails when the object was handled by an auto_ptr<object const>

Greetings! Firstly, this is not a regression, the behaviour was present in 1.30, too. I have attached a simple program that should demonstrate the behaviour, but the core is these lines, with foo being derived from enable_shared_from_this: // 1 std::auto_ptr<foo> g(new foo); boost::shared_ptr<foo> f(g); // 2 std::auto_ptr<foo> g(new foo); boost::shared_ptr<foo const> f(g); // 3 std::auto_ptr<foo const> g(new foo); boost::shared_ptr<foo const> f(g); In all three cases, I'd expect to be able to call shared_from_this(). However, only the first two cases really work. I think it boils down to the function detail::sp_enable_shared_from_this() in the ctor of shared_ptr<> that takes an auto_ptr<>. I'm not certain if there is a fix for this and what that fix would be, but I think the problem is that the 'right' function is not taken because at one place we have a 'foo' and at the other we have a 'foo const'. BTW: I believe that a conversion from auto_ptr<T> to shared_ptr<T const> doesn't compile on VC6, but I can't test that ATM(I'm not at work). If any kind soul wants to, they could add such a conversion to the regression-tests. Same goes for volatile probably. greetings and thumbs up Ulrich Eckhardt

Ulrich Eckhardt wrote:
Greetings! Firstly, this is not a regression, the behaviour was present in 1.30, too. I have attached a simple program that should demonstrate the behaviour, but the core is these lines, with foo being derived from enable_shared_from_this:
// 1 std::auto_ptr<foo> g(new foo); boost::shared_ptr<foo> f(g); // 2 std::auto_ptr<foo> g(new foo); boost::shared_ptr<foo const> f(g); // 3 std::auto_ptr<foo const> g(new foo); boost::shared_ptr<foo const> f(g);
In all three cases, I'd expect to be able to call shared_from_this(). However, only the first two cases really work. I think it boils down to the function detail::sp_enable_shared_from_this() in the ctor of shared_ptr<> that takes an auto_ptr<>.
This is, well, "by design". enable_shared_from_this requires that at least one shared_ptr "owns" the plain address of the object (without any cv qualifiers.) It does not matter what the static type of the shared_ptr is, i.e. what the T is in shared_ptr<T>; the actual pointer type needs to be "foo*". shared_ptr<void const volatile> p( new foo ); is enough for shared_from_this() to work (provided that you find a way to call it, of course.) The reason for this restriction is that enable_shared_from_this<foo> contains a weak_ptr<foo>, and in order to initialize it from a shared_ptr that stores a pointer to const foo, the implementation would need to const_cast the constness away twice, once in order to access the weak_ptr member, a second time to initialize a weak_ptr<foo> from a shared_ptr<foo const>.
BTW: I believe that a conversion from auto_ptr<T> to shared_ptr<T const> doesn't compile on VC6, but I can't test that ATM(I'm not at work). If any kind soul wants to, they could add such a conversion to the regression-tests. Same goes for volatile probably.
I see multiple auto_ptr<T> to shared_ptr<T const> conversions in shared_ptr_test.cpp, have you looked at it?

"Peter Dimov" <pdimov@mmltd.net> wrote in message news:00fc01c3f16c$e59d0220$1d00a8c0@pdimov2...
Ulrich Eckhardt wrote:
Greetings! Firstly, this is not a regression, the behaviour was present in 1.30, too. I have attached a simple program that should demonstrate the behaviour, but the core is these lines, with foo being derived from enable_shared_from_this:
// 1 std::auto_ptr<foo> g(new foo); boost::shared_ptr<foo> f(g); // 2 std::auto_ptr<foo> g(new foo); boost::shared_ptr<foo const> f(g); // 3 std::auto_ptr<foo const> g(new foo); boost::shared_ptr<foo const> f(g);
In all three cases, I'd expect to be able to call shared_from_this(). However, only the first two cases really work. I think it boils down to the function detail::sp_enable_shared_from_this() in the ctor of shared_ptr<> that takes an auto_ptr<>.
This is, well, "by design". enable_shared_from_this requires that at least one shared_ptr "owns" the plain address of the object (without any cv qualifiers.)
Why not make the _internal_weak_this member of enable_shared_from_this mutable? As I understand it, an enable_shared_from_this<X> basically undergoes two-phase construction: phase one at actual construction time, and phase two when it is put into a shared_ptr. As an extra safety measure, you could make _internal_weak_this a private member and make all shared_ptr<Y>'s friends of enable_shared_from_this (for compilers that allow template friends). Joe Gottman

Joe Gottman wrote:
Why not make the _internal_weak_this member of enable_shared_from_this mutable?
Because I haven't yet proven that this doesn't allow const correctness violations without an explicit const_cast from the user.

On Feb 12, 2004, at 8:34 AM, Peter Dimov wrote:
The reason for this restriction is that enable_shared_from_this<foo> contains a weak_ptr<foo>, and in order to initialize it from a shared_ptr that stores a pointer to const foo, the implementation would need to const_cast the constness away twice, once in order to access the weak_ptr member, a second time to initialize a weak_ptr<foo> from a shared_ptr<foo const>.
I'm having trouble finding a case where this suggestion produces undesirable results. If deriving from enable_shared_from_this<const foo>, everything seems to work, except that you can not: shared_ptr<foo> sp2(sp->shared_from_this()); where sp was created with: shared_ptr<foo> sp(ap); and ap is: auto_ptr<foo> ap(new foo); which seems very reasonable to me. If foo instead derives from enable_shared_from_this<foo>, then I can find no questionable behavior for: typedef [const] foo F1; typedef [const] foo F2; typedef [const] foo F3; auto_ptr<F1> ap(new foo); shared_ptr<F2> sp(ap); shared_ptr<F3> sp2(sp->shared_from_this()); for all combinations of const and non-const. Your const_cast suggestion appears to work when it is supposed to, and fail (at compile time) when it is supposed to, at least on the Metrowerks implementation of std::tr1::shared_ptr. If you see a suspicious test that you would like me to try on this modified and experimental implementation, I'd be happy to report back. -Howard

Howard Hinnant wrote:
On Feb 12, 2004, at 8:34 AM, Peter Dimov wrote:
The reason for this restriction is that enable_shared_from_this<foo> contains a weak_ptr<foo>, and in order to initialize it from a shared_ptr that stores a pointer to const foo, the implementation would need to const_cast the constness away twice, once in order to access the weak_ptr member, a second time to initialize a weak_ptr<foo> from a shared_ptr<foo const>.
I'm having trouble finding a case where this suggestion produces undesirable results.
I'm slowly reaching the same conclusion. [...]
which seems very reasonable to me. If foo instead derives from enable_shared_from_this<foo>, then I can find no questionable behavior for:
typedef [const] foo F1; typedef [const] foo F2; typedef [const] foo F3;
auto_ptr<F1> ap(new foo); shared_ptr<F2> sp(ap); shared_ptr<F3> sp2(sp->shared_from_this());
for all combinations of const and non-const.
One interesting case that is missing from your example is "new F1", when F1 is const foo. The auto_ptr is a distraction: shared_ptr<F2> sp(new F1); shared_ptr<F3> sp2(sp->shared_from_this()); As Joe Gottman correctly pointed out, the weak_ptr<foo> in enable_shared_from_this<foo> needs to be mutable, since it can be part of a genuine const object. That aside, I think I almost convinced myself that the second const_cast (initializing weak_ptr<foo> from a foo const*) is safe, since the two shared_from_this() overloads take care of the const correctness. foo * p = new foo; shared_ptr<foo const> sp( (foo const*)p ); p->shared_from_this(); is still fine.

Ulrich Eckhardt wrote:
Greetings! Firstly, this is not a regression, the behaviour was present in 1.30, too. I have attached a simple program that should demonstrate the behaviour, but the core is these lines, with foo being derived from enable_shared_from_this:
// 1 std::auto_ptr<foo> g(new foo); boost::shared_ptr<foo> f(g); // 2 std::auto_ptr<foo> g(new foo); boost::shared_ptr<foo const> f(g); // 3 std::auto_ptr<foo const> g(new foo); boost::shared_ptr<foo const> f(g);
In all three cases, I'd expect to be able to call shared_from_this(). However, only the first two cases really work.
I've now modified the CVS version of shared_ptr to handle these cases. With the modifications in place, your example now prints: plain foo failed const failed shared_ptr<foo> works const works shared_ptr<foo const> const works shared_ptr<foo> from auto_ptr<foo> works const works shared_ptr<foo const> from auto_ptr<foo> const works shared_ptr<foo const> from auto_ptr<foo const> const works Thank you for the suggestion. :-)
participants (4)
-
Howard Hinnant
-
Joe Gottman
-
Peter Dimov
-
Ulrich Eckhardt