[boost] [smart_ptr] Moving cast for unique_ptr

562 views
Skip to first unread message

Karolin Varner

unread,
Oct 6, 2015, 7:17:37 AM10/6/15
to bo...@lists.boost.org
Hi all!

There is the family of boost pointer cast (boost::static_pointer_cast, boost::dynamic_pointer_cast, ...) that allow a developer to write generic code by casting pointers regardless of whether they are some smart pointer (e.g. shared_ptr) or plain pointers.

However, those functions have not been implemented for unique_ptr, because they create a copy of their pointer and unique_pointers can not be copied.

In my case I still needed to cast some unique_ptr and since it's ok to move unique_ptrs I wrote a function that does exactly that.
The code is below – Comments are very, very welcome. It works for my case, but I am sure it is not as generic and safe as it should be ;) . Specifically, for a library, it should be made to work on any kind of smart/plain pointer. Not just unique_ptr.

Is there any interest to include a boost::static_moving_pointer_cast and the other types of casts in boost?

Best,
Karolin

/// Dynamic cast a unique_ptr.
///
/// This function shall move the pointer in in_ptr, into
/// a new unique_pointer of the desire type, dynamic_casting
/// it in the process.
/// In given pointer shall contain a NULL pointer afterwards.
///
/// If unsuccessful, function shall throw an error and leave
/// in_ptr unmodified.
///
/// This behaviour contrasts a bit with the behaviour of the
/// normal dynamic_cast:
/// 1. dynamic_cast copies objects, this moves them since
/// unique_pointers can not be copied.
/// 2. dynamic_cast returns a NULL pointer if the pointer to
/// cast is of an incomplete type, this throws
/// incomplete_cast instead, to ease error handling.
///
/// @tparam T The type to convert to (as non-pointer)
/// @tparam F The type to convert from (as non-pointer)
/// @param in_ptr The unique_ptr to cast
/// @return in_ptr, cast to the desired type
template<typename T, typename F>
std::unique_ptr<T> dynamic_moving_pointer_cast(std::unique_ptr<F> &in_ptr) {
// Exception safety: If The dynamic cast throws an error,
// or of we throw incomplete_cast, in_ptr will still be
// unmodified
T *plain_out_ptr = dynamic_cast<F*>(in_ptr.get());
if (plain_out_ptr == NULL && in_ptr.get() != NULL)
throw incomplete_cast();

// Exception safety: Both operations are noexcept
in_ptr.release();
return std::unique_ptr<T>(plain_out_ptr);
}


_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Francois Duranleau

unread,
Oct 6, 2015, 7:46:15 AM10/6/15
to bo...@lists.boost.org

The argument should be &&, not &, or else you won't be able to bind to r-values.

> // Exception safety: If The dynamic cast throws an error,
> // or of we throw incomplete_cast, in_ptr will still be
> // unmodified
> T *plain_out_ptr = dynamic_cast<F*>(in_ptr.get());
> if (plain_out_ptr == NULL && in_ptr.get() != NULL)
> throw incomplete_cast();
>
> // Exception safety: Both operations are noexcept
> in_ptr.release();
> return std::unique_ptr<T>(plain_out_ptr);
> }

It would nice to generalize this to arbitrary smart pointers, because
even for copyable
smart pointers (e.g. shared_ptr, intrusive_ptr), there is interest in
"cast moving" to
avoid needlessly touching the reference count in such case (better performance).
However, I am not sure we can implement this non-intrusively.

--
François

Vicente J. Botet Escriba

unread,
Oct 7, 2015, 2:16:34 AM10/7/15
to bo...@lists.boost.org
Le 06/10/15 12:19, Karolin Varner a écrit :

> Hi all!
>
> There is the family of boost pointer cast (boost::static_pointer_cast, boost::dynamic_pointer_cast, ...) that allow a developer to write generic code by casting pointers regardless of whether they are some smart pointer (e.g. shared_ptr) or plain pointers.
>
> However, those functions have not been implemented for unique_ptr, because they create a copy of their pointer and unique_pointers can not be copied.
>
> In my case I still needed to cast some unique_ptr and since it's ok to move unique_ptrs I wrote a function that does exactly that.
> The code is below – Comments are very, very welcome. It works for my case, but I am sure it is not as generic and safe as it should be ;) . Specifically, for a library, it should be made to work on any kind of smart/plain pointer. Not just unique_ptr.
>
> Is there any interest to include a boost::static_moving_pointer_cast and the other types of casts in boost?
I believe this will be a useful addition.

>
> /// Dynamic cast a unique_ptr.
> ///
> /// This function shall move the pointer in in_ptr, into
> /// a new unique_pointer of the desire type, dynamic_casting
> /// it in the process.
> /// In given pointer shall contain a NULL pointer afterwards.
> ///
> /// If unsuccessful, function shall throw an error and leave
> /// in_ptr unmodified.
> ///
> /// This behaviour contrasts a bit with the behaviour of the
> /// normal dynamic_cast:
> /// 1. dynamic_cast copies objects, this moves them since
> /// unique_pointers can not be copied.
> /// 2. dynamic_cast returns a NULL pointer if the pointer to
> /// cast is of an incomplete type, this throws
> /// incomplete_cast instead, to ease error handling.
I wouldn't throw in this case to be coherent with dynamic_pointer_cast.
If you need a better error handling for dynamic_moving_pointer_cast you
need also for dynamic_pointer_cast.

Just wondering if we can not make dynamic_pointer_cast to work as your
dynamic_moving_pointer_cast, when the parameter is a rvalue reference

template<class T, class U>

shared_ptr<T> dynamic_pointer_cast(shared_ptr<U> && r) noexcept;

C++ International Standard
The use will be

dynamic_pointer_cast<U>(ret_up());
dynamic_pointer_cast<U>(move(up));

Vicente

Gavin Lambert

unread,
Oct 7, 2015, 3:03:56 AM10/7/15
to bo...@lists.boost.org
On 7/10/2015 19:16, Vicente J. Botet Escriba wrote:
> Just wondering if we can not make dynamic_pointer_cast to work as your
> dynamic_moving_pointer_cast, when the parameter is a rvalue reference
>
> template<class T, class U>
> shared_ptr<T> dynamic_pointer_cast(shared_ptr<U> && r) noexcept;
>
> C++ International Standard
> The use will be
>
> dynamic_pointer_cast<U>(ret_up());
> dynamic_pointer_cast<U>(move(up));

It would theoretically be possible if you have access to the internals
of shared_ptr (otherwise it would be no better than copying).

However I'm dubious about this usage -- dynamic casts naturally admit
the possibility of failure, which results in discarding the input
parameter in cases that it's a temporary (hence the changed semantics in
the proposed dynamic_moving_pointer_cast).

This basically translates into an operation that says "return the
pointer without touching its refcount if the cast succeeds, otherwise
decrement the refcount". In particular the latter case can result in
almost immediately deleting a value newly-created by the called
function, or otherwise unique prior to the cast.

The equivalent operation on unique_ptr would simply be "delete input
pointer if cast fails".

Either of these seem like an odd thing for code to want to do in
practice. Usually polymorphic code doesn't transfer ownership at the
same time. I'm curious if there would be any good real-world examples
for this.

Vicente J. Botet Escriba

unread,
Oct 7, 2015, 2:06:49 PM10/7/15
to bo...@lists.boost.org
Le 07/10/15 09:03, Gavin Lambert a écrit :

> On 7/10/2015 19:16, Vicente J. Botet Escriba wrote:
>> Just wondering if we can not make dynamic_pointer_cast to work as your
>> dynamic_moving_pointer_cast, when the parameter is a rvalue reference
>>
>> template<class T, class U>
>> shared_ptr<T> dynamic_pointer_cast(shared_ptr<U> && r) noexcept;
>>
>> C++ International Standard
>> The use will be
>>
>> dynamic_pointer_cast<U>(ret_up());
>> dynamic_pointer_cast<U>(move(up));
>
> It would theoretically be possible if you have access to the internals
> of shared_ptr (otherwise it would be no better than copying).
Well we have boost::unique_ptr to play with.

>
> However I'm dubious about this usage -- dynamic casts naturally admit
> the possibility of failure, which results in discarding the input
> parameter in cases that it's a temporary (hence the changed semantics
> in the proposed dynamic_moving_pointer_cast).
IIUC passing the unique_ptr by reference allows to don't move if the
dynamic_cast fails. I don't see how this implies an exception.

>
> This basically translates into an operation that says "return the
> pointer without touching its refcount if the cast succeeds, otherwise
> decrement the refcount". In particular the latter case can result in
> almost immediately deleting a value newly-created by the called
> function, or otherwise unique prior to the cast.
Good point.

>
> The equivalent operation on unique_ptr would simply be "delete input
> pointer if cast fails".
>
> Either of these seem like an odd thing for code to want to do in
> practice. Usually polymorphic code doesn't transfer ownership at the
> same time. I'm curious if there would be any good real-world examples
> for this.
>
I agree with your analysis.

Vicente

Peter Dimov

unread,
Oct 7, 2015, 3:46:24 PM10/7/15
to bo...@lists.boost.org
Vicente J. Botet Escriba wrote:

> Just wondering if we can not make dynamic_pointer_cast to work as your
> dynamic_moving_pointer_cast, when the parameter is a rvalue reference

Yes, that was my first thought as well.

template<class T, class U>
unique_ptr<T> dynamic_pointer_cast( unique_ptr<U> && r ) noexcept
{
T * p = dynamic_cast<T*>( r.get() );
if( p ) r.release();
return unique_ptr<T>( p );

Karolin Varner

unread,
Oct 7, 2015, 8:09:54 PM10/7/15
to bo...@lists.boost.org
Hi

On 10/07/2015 09:45 PM, Peter Dimov wrote:
> Vicente J. Botet Escriba wrote:
>
>> Just wondering if we can not make dynamic_pointer_cast to work as your dynamic_moving_pointer_cast, when the parameter is a rvalue reference
>
> template<class T, class U>
> unique_ptr<T> dynamic_pointer_cast( unique_ptr<U> && r ) noexcept
> {
> T * p = dynamic_cast<T*>( r.get() );
> if( p ) r.release();
> return unique_ptr<T>( p );
> }

Agreed. That seems like the best option.

On 10/07/2015 09:03 AM, Gavin Lambert wrote:
> On 7/10/2015 19:16, Vicente J. Botet Escriba wrote:
>> Just wondering if we can not make dynamic_pointer_cast to work as your
>> dynamic_moving_pointer_cast, when the parameter is a rvalue reference
>
> It would theoretically be possible if you have access to the internals of shared_ptr (otherwise it would be no better than copying).

We do have access to the internals of the boost smart pointers, so we could provide a set of specialized moving casts for std::unique_ptr, boost::shared_ptr and boost::scoped_ptr.

For std::shared_ptr and the other pointers we might still emulate a moving cast in order to facilitate generic programming.

Best,
Karolin

Gavin Lambert

unread,
Oct 7, 2015, 8:58:17 PM10/7/15
to bo...@lists.boost.org
On 8/10/2015 09:27, Karolin Varner wrote:
> On 10/07/2015 09:45 PM, Peter Dimov wrote:
>> template<class T, class U>
>> unique_ptr<T> dynamic_pointer_cast( unique_ptr<U> && r ) noexcept
>> {
>> T * p = dynamic_cast<T*>( r.get() );
>> if( p ) r.release();
>> return unique_ptr<T>( p );
>> }
[...]
> On 10/07/2015 09:03 AM, Gavin Lambert wrote:
>> On 7/10/2015 19:16, Vicente J. Botet Escriba wrote:
>>> Just wondering if we can not make dynamic_pointer_cast to work as your
>>> dynamic_moving_pointer_cast, when the parameter is a rvalue reference
>>
>> It would theoretically be possible if you have access to the
>> internals of shared_ptr (otherwise it would be no better than copying).
>
> We do have access to the internals of the boost smart pointers, so
> wecould provide a set of specialized moving casts for std::unique_ptr,
> boost::shared_ptr and boost::scoped_ptr.

unique_ptr is straightforward, as Peter Dimov has demonstrated above.
scoped_ptr is similarly straightforward. As I mentioned on the other
thread branch though, I'm a bit skeptical that either of these types
would be useful for a dynamic moving cast -- dynamic cast yes, moving
yes, but both at once is very peculiar semantics.

It's shared_ptr that you need internal access for, if you want to be
able to avoid the refcount changes completely. And this one is more
likely to be useful for dynamic moving casts, though it's still hard to
think of a motivating case.

> For std::shared_ptr and the other pointers we might still emulate a
> moving cast in order to facilitate generic programming.

I'm not sure emulation is any better than existing dynamic_pointer_cast;
this was my attempt:

template<class T, class U>
shared_ptr<T> dynamic_pointer_cast(shared_ptr<U>&& r) noexcept
{
T * p = dynamic_cast<T*>(r.get());
return p ? shared_ptr<T>(r, p) : nullptr;
}

... which looks pretty identical to the non-rvalue dynamic_pointer_cast
to me. I don't really see any opportunities for moving r in this
implementation (without adding an extra constructor to shared_ptr), but
maybe I've missed something.

OTOH, adding this constructor to the Boost version:

template< class Y >
shared_ptr( shared_ptr<Y> && r, element_type * p ) BOOST_NOEXCEPT
: px( p ), pn( move(r.pn) )
{
}

(or similar, I haven't tested that) would allow the above to use
shared_ptr<T>(move(r), p), which would be a potential improvement. But
this doesn't help for std::shared_ptr.

Kohei Takahashi

unread,
Oct 7, 2015, 10:09:30 PM10/7/15
to bo...@lists.boost.org
Hi Karolin,

On 2015/10/06 19:19, Karolin Varner wrote:
> There is the family of boost pointer cast (boost::static_pointer_cast, boost::dynamic_pointer_cast, ...) that allow a developer to write generic code by casting pointers regardless of whether they are some smart pointer (e.g. shared_ptr) or plain pointers.
>
> However, those functions have not been implemented for unique_ptr, because they create a copy of their pointer and unique_pointers can not be copied.
I disagree this option because user can write unsafe code easily, see
http://melpon.org/wandbox/permlink/x6Fj1QSwdD9fBAfA .
It based on your dynamic_cast version, but it is also dangerous for
other casts (static, const, ...).

Best,
Kohei Takahashi


signature.asc

Peter Dimov

unread,
Oct 8, 2015, 9:02:25 AM10/8/15
to bo...@lists.boost.org
Karolin Varner wrote:

> For std::shared_ptr and the other pointers we might still emulate a moving
> cast in order to facilitate generic programming.

Like Gavin, I'm not sure I quite see the point of using dynamic_pointer_cast
on an rvalue in generic code, because if it fails, the original is lost.

Karolin Varner

unread,
Oct 8, 2015, 3:54:11 PM10/8/15
to bo...@lists.boost.org
Hi Gavin

On 10/08/2015 03:01 PM, Peter Dimov wrote:
> Karolin Varner wrote:
>
>> For std::shared_ptr and the other pointers we might still emulate a moving cast in order to facilitate generic programming.
>
> Like Gavin, I'm not sure I quite see the point of using dynamic_pointer_cast on an rvalue in generic code, because if it fails, the original is lost.

Yeah, I was thinking of using an implementation, where the input pointer would only be moved, if the cast is successful.
In case the cast wasn't successfully. we could return a NULL pointer and leave the input as it was.

Best,
Karolin

Karolin Varner

unread,
Dec 21, 2015, 7:10:23 AM12/21/15
to bo...@lists.boost.org
Hi,

I have created a pull request based on the discussion in this thread: https://github.com/boostorg/smart_ptr/pull/23
It doesn't yet contain optimizations for copyable pointers, but it contains pointer_cast overloads for std::shared_ptr and std::unique_ptr.

Since this came up in this thread, I also created a motivating example for static_pointer_cast(std::unique_ptr<...>&&) and dynamic_pointer_cast(std::unique_ptr<...>&&)
http://melpon.org/wandbox/permlink/R3QWA7MaxZ5o1ocG

It also seems that, even there are no overloads with move semantics for shared_ptr, calling the pointer casts with std::move(...) works. So in principle we could use move(...) for those and implement optimizations later.
Though I am not quite convinced, that this is good behavior.

Best,
Karolin
Reply all
Reply to author
Forward
0 new messages