Re: [boost] Why no non-null smart pointers?
Ross MacGregor wrote
"Johan Råde" <
rade@.lth
> wrote in message news:ft10ag$6nn$
1@.gmane
...
I know it is an insane idea to suggest changing the entire boost::smart_ptr library to a boost::smart_ref library, but I think it may actually be the right thing to do. Does anyone agree?
I also find the nill state of shared_ptr to be a liability when designing interfaces. The primary purpose of shared_ptr when used in an interface context is to convey ownership, and almost always it feels wrong to also provide simultaneously an option to convey ownership of nothing. The poor user is left with the responsibility of testing every shared_ptr he has received for null state before using it, but its easy to forget and the compiler provides no assistance. Ultimately, the library interface based on shared_ptr is more error prone, less likely to be easy to use, and therefore arguably suboptimal. Of course no one suggests that shared_ptr should (or could) be changed in ways that impacts backwards compatibility, but I have to agree with the author of the post that this issue can be identified as a legitimate cause for concern. Jeff -- View this message in context: http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-tp2642959p... Sent from the Boost - Dev mailing list archive at Nabble.com.
Perhaps there is even a backwards compatible solution. Could we employ an optional null object pattern in methods and or constructors of shared_ptr that might establish a raw nill state? Perhaps something like this obviously oversimplified example could be use. template < typename T> void shared_ptr < T > :: reset ( T * p = 0 ) { if ( p ) { /* what happens in shared_ptr now */ } else { /* new behavior in shared_ptr, when it detects a nill raw pointer */ static NullObjectTypes < T > nullObject; *this = shared_ptr < T > ( &nullObject, NullObjectTypes < T > :: deleter () ); } } Jeff -- View this message in context: http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-tp2642959p... Sent from the Boost - Dev mailing list archive at Nabble.com.
Maybe there /is/ even a backwards compatible solution. Perhaps an optional null object pattern could be employed in methods, and or constructors, of shared_ptr that might establish a raw nill state? Perhaps something like this obviously oversimplified example could be use. template < typename T> void shared_ptr < T > :: reset ( T * p = 0 ) { if ( p ) { /* what happens in shared_ptr now */ } else { /* new behavior in shared_ptr, when it detects a nill raw pointer */ *this = NullObjectTypes < T > :: factory (); } } Jeff -- View this message in context: http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-tp2642959p... Sent from the Boost - Dev mailing list archive at Nabble.com.
On Mon, Apr 28, 2014 at 9:42 PM, Jeff Hill
Maybe there /is/ even a backwards compatible solution.
Perhaps an optional null object pattern could be employed in methods, and or constructors, of shared_ptr that might establish a raw nill state? Perhaps something like this obviously oversimplified example could be use.
template < typename T> void shared_ptr < T > :: reset ( T * p = 0 ) { if ( p ) { /* what happens in shared_ptr now */ } else { /* new behavior in shared_ptr, when it detects a nill raw pointer */ *this = NullObjectTypes < T > :: factory (); } }
Please, don't change shared_ptr (or any other *_ptr) behavior. They are intended to behave as pointers, and pointers do have a null state. This is a useful feature in many cases. I'd prefer the functionality you're looking for to be implemented as a separate component/library. It may be based on pointers internally though.
On April 28, 2014 11:20:35 AM EDT, Jeff Hill
Ross MacGregor wrote
"Johan Råde"
I know it is an insane idea to suggest changing the entire boost::smart_ptr library to a boost::smart_ref library, but I think it may actually be the right thing to do. Does anyone agree?
I also find the nill state of shared_ptr to be a liability when designing interfaces. The primary purpose of shared_ptr when used in an interface context is to convey ownership, and almost always it feels wrong to also provide simultaneously an option to convey ownership of nothing. The poor user is left with the responsibility of testing every shared_ptr he has received for null state before using it, but its easy to forget and the compiler provides no assistance.
You've overlooked the use case of reclaiming ownership from smart_ptr. There's nothing left for it to own after that. (You can't replace it with a default constructed instance, as there may be no default constructor. Any other value you choose would be a sentinel value and you'd want to detect that in many contexts, so checking for null is more straightforward.) OTOH, you could justify a new class that has no reset() and that rejects null pointers, so it is never null, yet has shared ownership semantics. ___ Rob (Sent from my portable computation engine)
You've overlooked the use case of reclaiming ownership from smart_ptr.
Sorry, apparently a bit more explanation is needed. In the code above I have this assignment. *this = NullObjectTypes < T > :: factory (); My idea is that shared_ptr <T> is returned from this factory function so that it can be assigned to *this, and so ownership /is/ properly maintained. The default templated version of NullObjectTypes < T > :: factory () would return a default constructed shared_ptr <T>. This is necessary of course for backwards compatibility. template < class T > struct NullObjectTypes shared_ptr < ChessPiece > factory () { return shared_ptr < T > (); } }; The benefit comes when T is an interface class. In this situation we can specialize NullObjectTypes < T > as follows. Lets say that T is the ChessPiece class, and we will implement the null object pattern for it. struct ChessPiece { virtual void move() = 0; }; struct ChessPieceNull : public ChessPiece { void move() {} }; template <> NullObjectTypes < ChessPiece > shared_ptr < ChessPiece > factory (); }; template <> shared_ptr < ChessPiece > NullObjectTypes < ChessPiece > :: factory () { static ChessPieceNull chessPieceNull; return shared_ptr < ChessPiece > ( &chessPieceNull, nullDeleter () ); } -- View this message in context: http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-tp2642959p... Sent from the Boost - Dev mailing list archive at Nabble.com.
So I deleted the wrong post. This is the version of the proposed change that I intended to keep. template < typename T> void shared_ptr < T > :: reset ( T * p = 0 ) { if ( p ) { /* what happens in shared_ptr now */ } else { /* new behavior in shared_ptr, when it detects a nill raw pointer */ *this = NullObjectTypes < T > :: factory (); } } -- View this message in context: http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-tp2642959p... Sent from the Boost - Dev mailing list archive at Nabble.com.
On Tue, Apr 29, 2014 at 07:44:27AM -0700, Jeff Hill wrote:
So I deleted the wrong post. This is the version of the proposed change that I intended to keep.
Note that mailing lists (which this actually is) doesn't _do_ deletion. If you want to rescind a post, reply to it and inform people that the parent should be unconsidered. Any operation that your particular groups/news-alike gateway promises is a "delete" is a completely local thing. No-one outside of your gateway will ever see it, maybe not even outside of you. -- Lars Viklund | zao@acc.umu.se
On 29 April 2014 15:32, Jeff Hill wrote:
template <> shared_ptr < ChessPiece > NullObjectTypes < ChessPiece > :: factory () { static ChessPieceNull chessPieceNull; return shared_ptr < ChessPiece > ( &chessPieceNull, nullDeleter () ); }
N.B. This means every shared_ptr that owns the NullObject gets created with unique ownership, creating a new control block on the heap. Sometimes that might be what you want, but other times it would be better to copy an existing object so all null objects of a given type share ownership: template <> shared_ptr < ChessPiece > NullObjectTypes < ChessPiece > :: factory () { static ChessPieceNull chessPieceNull; static shared_ptr < ChessPiece > nullsp( &chessPieceNull, nullDeleter () ); return nullsp; } (This comment shouldn't be seen as endorsement of the proposal ;-)
static ChessPieceNull chessPieceNull; static shared_ptr < ChessPiece > nullsp( &chessPieceNull, nullDeleter () ); return nullsp;
Thanks for your suggestion. Agree this is usually preferable, but since the code is in the specialization (within a user snap-in) then of course the user can decide which approach he prefers. -- View this message in context: http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-tp2642959p... Sent from the Boost - Dev mailing list archive at Nabble.com.
I was considering this while I was at the dentist, and I can identify a flaw with my proposal. The "NullObjectTypes < T > :: factory ()" returns a default constructed shared_ptr by default, and if this default version is called from any of the constructor's that detect attempts to enter the shared pointer into a nill internal raw pointer state, then there will be infinite recursion. -- View this message in context: http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-tp2642959p... Sent from the Boost - Dev mailing list archive at Nabble.com.
On Mon, Apr 28, 2014 at 8:20 AM, Jeff Hill
I also find the nill state of shared_ptr to be a liability when designing interfaces. The primary purpose of shared_ptr when used in an interface context is to convey ownership, and almost always it feels wrong to also provide simultaneously an option to convey ownership of nothing. The poor user is left with the responsibility of testing every shared_ptr he has received for null state before using it, but its easy to forget and the compiler provides no assistance. Ultimately, the library interface based on shared_ptr is more error prone, less likely to be easy to use, and therefore arguably suboptimal.
Of course no one suggests that shared_ptr should (or could) be changed in ways that impacts backwards compatibility, but I have to agree with the author of the post that this issue can be identified as a legitimate cause for concern.
Jeff
There is usefullness to null and non-null smart pointers. Just so we're not repeating thoughts here too much, this was also discussed at the end of last year: http://lists.boost.org/Archives/boost/2013/10/206732.php I think the general consensus is that people want non-null smart pointers (myself included), but there are many subtleties that people disagree on. Be sure to read that thread going down paths that have been traveled before. -- -Matt Calabrese
I did mention that, "no one suggests that shared_ptr should (or could) be changed in ways that impacts backwards compatibility." What I /am/ considering is if there is a way to make the shared_ptr implement the null object paradigm, but only for a particular user type T, but otherwise not impact current behavior of shared_ptr including revealing in its public interface all that is good and bad with the nill state of raw pointers. In the example above type ChessPiece might enforce the null object pattern for shared_ptr but user types that don't specialize the null object factory class would retain exactly current behavior. Thanks for the reference, I will definitely have a close look. Jeff -- View this message in context: http://boost.2283326.n4.nabble.com/Why-no-non-null-smart-pointers-tp2642959p... Sent from the Boost - Dev mailing list archive at Nabble.com.
participants (6)
-
Andrey Semashev
-
Jeff Hill
-
Jonathan Wakely
-
Lars Viklund
-
Matt Calabrese
-
Rob Stewart