pointer_casts with move semantics for unique_ptr

This commit is contained in:
Karolin Varner
2015-12-17 15:32:30 +01:00
parent 6b787f1cec
commit ce52fb1045
3 changed files with 222 additions and 15 deletions

View File

@ -7,6 +7,8 @@
// //
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
#include <boost/config.hpp>
#ifndef BOOST_POINTER_CAST_HPP #ifndef BOOST_POINTER_CAST_HPP
#define BOOST_POINTER_CAST_HPP #define BOOST_POINTER_CAST_HPP
@ -44,10 +46,27 @@ inline T* reinterpret_pointer_cast(U *ptr)
#if !defined( BOOST_NO_CXX11_SMART_PTR ) #if !defined( BOOST_NO_CXX11_SMART_PTR )
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_base_of.hpp>
#include <boost/type_traits/is_pod.hpp>
#include <boost/type_traits/has_virtual_destructor.hpp>
#include <memory> #include <memory>
namespace boost { namespace boost {
namespace detail {
template<class T, class U>
void assert_safe_moving_upcast() {
BOOST_STATIC_ASSERT_MSG( !(boost::is_base_of<T, U>::value && !boost::is_pod<U>::value && !boost::has_virtual_destructor<T>::value)
, "Upcast from a non-POD child to a base without virtual destructor is unsafe, because the child's destructor "
"will not be called when the base pointer is deleted. Consider using shared_ptr for such types.");
}
}
//static_pointer_cast overload for std::shared_ptr //static_pointer_cast overload for std::shared_ptr
using std::static_pointer_cast; using std::static_pointer_cast;
@ -68,6 +87,35 @@ template<class T, class U> std::shared_ptr<T> reinterpret_pointer_cast(const std
return std::shared_ptr<T>( r, p ); return std::shared_ptr<T>( r, p );
} }
//static_pointer_cast overload for std::unique_ptr
template<class T, class U> std::unique_ptr<T> static_pointer_cast( std::unique_ptr<U> && r ) BOOST_NOEXCEPT
{
detail::assert_safe_moving_upcast<T, U>();
return std::unique_ptr<T>( static_cast<T*>( r.release() ) );
}
//dynamic_pointer_cast overload for std::unique_ptr
template<class T, class U> std::unique_ptr<T> dynamic_pointer_cast( std::unique_ptr<U> && r ) BOOST_NOEXCEPT
{
detail::assert_safe_moving_upcast<T, U>();
T * p = dynamic_cast<T*>( r.get() );
if( p ) r.release();
return std::unique_ptr<T>( p );
}
//const_pointer_cast overload for std::unique_ptr
template<class T, class U> std::unique_ptr<T> const_pointer_cast( std::unique_ptr<U> && r ) BOOST_NOEXCEPT
{
return std::unique_ptr<T>( const_cast<T*>( r.release() ) );
}
//reinterpret_pointer_cast overload for std::unique_ptr
template<class T, class U> std::unique_ptr<T> reinterpret_pointer_cast( std::unique_ptr<U> && r ) BOOST_NOEXCEPT
{
return std::unique_ptr<T>( reinterpret_cast<T*>( r.release() ) );
}
} // namespace boost } // namespace boost
#endif // #if !defined( BOOST_NO_CXX11_SMART_PTR ) #endif // #if !defined( BOOST_NO_CXX11_SMART_PTR )

View File

@ -9,7 +9,7 @@
width="277" align="middle" border="0">pointer_cast</h1> width="277" align="middle" border="0">pointer_cast</h1>
<p>The pointer cast functions (<code>boost::static_pointer_cast</code> <code>boost::dynamic_pointer_cast</code> <p>The pointer cast functions (<code>boost::static_pointer_cast</code> <code>boost::dynamic_pointer_cast</code>
<code>boost::reinterpret_pointer_cast</code> <code>boost::const_pointer_cast</code>) <code>boost::reinterpret_pointer_cast</code> <code>boost::const_pointer_cast</code>)
provide a way to write generic pointer castings for raw pointers and std::shared_ptr. The functions provide a way to write generic pointer castings for raw pointers, std::shared_ptr and std::unique_ptr. The functions
are defined in <CITE><A href="../../boost/pointer_cast.hpp">boost/pointer_cast.hpp</A>.</CITE></p> are defined in <CITE><A href="../../boost/pointer_cast.hpp">boost/pointer_cast.hpp</A>.</CITE></p>
<P>There is test/example code in <CITE><A href="test/pointer_cast_test.cpp">pointer_cast_test.cpp</A></CITE>.</p> <P>There is test/example code in <CITE><A href="test/pointer_cast_test.cpp">pointer_cast_test.cpp</A></CITE>.</p>
<h2><a name="rationale">Rationale</a></h2> <h2><a name="rationale">Rationale</a></h2>
@ -23,7 +23,7 @@ template&lt;class T, class U&gt;
<P>Pointer cast functions from <CITE><A href="../../boost/pointer_cast.hpp">boost/pointer_cast.hpp</A></CITE> <P>Pointer cast functions from <CITE><A href="../../boost/pointer_cast.hpp">boost/pointer_cast.hpp</A></CITE>
are overloads of <code>boost::static_pointer_cast</code>, <code>boost::dynamic_pointer_cast</code>, are overloads of <code>boost::static_pointer_cast</code>, <code>boost::dynamic_pointer_cast</code>,
<code>boost::reinterpret_pointer_cast</code> and <code>boost::const_pointer_cast</code> <code>boost::reinterpret_pointer_cast</code> and <code>boost::const_pointer_cast</code>
for raw pointers and std::shared_ptr. This way when developing pointer type independent classes, for raw pointers, std::shared_ptr and std::unique_ptr. This way when developing pointer type independent classes,
for example, memory managers or shared memory compatible classes, the same code for example, memory managers or shared memory compatible classes, the same code
can be used for raw and smart pointers.</p> can be used for raw and smart pointers.</p>
<H2><A name="synopsis">Synopsis</A></H2> <H2><A name="synopsis">Synopsis</A></H2>
@ -58,12 +58,71 @@ inline std::shared_ptr&lt;U&gt; const_pointer_cast(std::shared_ptr&lt;T&gt; ptr)
template&lt;class T, class U&gt; template&lt;class T, class U&gt;
inline std::shared_ptr&lt;U&gt; reinterpret_pointer_cast(std::shared_ptr&lt;T&gt; ptr); inline std::shared_ptr&lt;U&gt; reinterpret_pointer_cast(std::shared_ptr&lt;T&gt; ptr);
template&lt;class T, class U&gt;
inline std::unique_ptr&lt;U&gt; static_pointer_cast(std::unique_ptr&lt;T&gt; &amp;&amp;ptr);
template&lt;class T, class U&gt;
inline std::unique_ptr&lt;U&gt; dynamic_pointer_cast(std::unique_ptr&lt;T&gt; &amp;&amp;ptr);
template&lt;class T, class U&gt;
inline std::unique_ptr&lt;U&gt; const_pointer_cast(std::unique_ptr&lt;T&gt; &amp;&amp;ptr);
template&lt;class T, class U&gt;
inline std::unique_ptr&lt;U&gt; reinterpret_pointer_cast(std::unique_ptr&lt;T&gt; &amp;&amp;ptr);
} // namespace boost } // namespace boost
</PRE> </PRE>
</BLOCKQUOTE> </BLOCKQUOTE>
<P>As you can see from the above synopsis, the pointer cast functions are just <P>As you can see from the above synopsis, the pointer cast functions for raw pointers are just
wrappers around standard C++ cast operators.</P> wrappers around standard C++ cast operators.</P>
<H2><A name="memory_safety">Memory Safety</A></H2>
<P>It is possible to write unsafe code, when upcasting to a base type without virtual destructor.
Consider the following example:</P>
<PRE>
#include &lt;memory&gt;
#include &lt;utility&gt;
#include &lt;boost/pointer_cast.hpp&gt;
#include &lt;boost/make_unique.hpp&gt;
int destructed = 0;
struct base {
~base() {
// ...
}
};
struct child : base {
virtual ~child() {
destructed++;
}
}
int main() {
{
std::unique_ptr<child> tmp = boost::make_unique<child>();
std::unique_ptr<base> sink = boost::static_pointer_cast<base>( std::move(tmp) );
}
// child::~child was never called
assert(destructed == 0);
return 0;
}
</PRE>
<P>In this example, the child destructor child::~child was never called, because the child* in tmp
was downcast to a base* and moved into sink. The destructor of tmp did essentially nothing, because
it contained nullptr during destruction; sink deleted the pointer, but since base::~base is non-virtual
the child destructor was never called.</P>
<P>boost::static_pointer_cast and boost::dynamic_pointer_cast for std::unique_ptr prevent the above scenario
by raising a compiler error when such a cast is detected.</P>
<P>The overloads for std::shared_ptr and boost::shared_ptr are not prone to this problem, since they internally
always store the original pointer with the original type.</P>
<P>The plain pointer casts are in principle also prone to that problem, but it is assumed that raw pointers
are non-owning, so no checking is performed.</P>
<H2><A name="example">Example</A></H2> <H2><A name="example">Example</A></H2>
<BLOCKQUOTE> <BLOCKQUOTE>
<PRE> <PRE>

View File

@ -1,5 +1,5 @@
// //
// cpp11_pointer_cast_test.cpp - a test for boost/pointer_cast.hpp with std::shared_ptr // cpp11_pointer_cast_test.cpp - a test for boost/pointer_cast.hpp with std::shared_ptr and std::unique_ptr
// //
// Copyright (c) 2016 Karolin Varner // Copyright (c) 2016 Karolin Varner
// //
@ -8,12 +8,28 @@
// http://www.boost.org/LICENSE_1_0.txt) // http://www.boost.org/LICENSE_1_0.txt)
// //
#include <boost/config.hpp>
#include <boost/pointer_cast.hpp> #include <boost/pointer_cast.hpp>
#include <boost/config.hpp>
#include <boost/detail/lightweight_test.hpp> #include <boost/detail/lightweight_test.hpp>
#include <boost/get_pointer.hpp> #include <boost/get_pointer.hpp>
#include <boost/shared_ptr.hpp>
#include <memory> #include <memory>
#include <utility>
#include <functional>
#if defined( BOOST_NO_CXX11_RVALUE_REFERENCES ) \
|| defined( BOOST_NO_CXX11_HDR_FUNCTIONAL ) \
|| defined( BOOST_NO_CXX11_HDR_UTILITY ) \
|| defined( BOOST_NO_CXX11_LAMBDAS ) \
|| defined( BOOST_NO_CXX11_RVALUE_REFERENCES )
// We expect all the features or none of the features to be
// available, since we should be on C++11
int main() { return 0; }
#else
namespace namespace
{ {
@ -43,7 +59,7 @@ public:
}; };
class derived class derived
: public base, public base2 : public base, public base2
{ {
int filler [5]; int filler [5];
}; };
@ -66,7 +82,7 @@ bool check_dynamic_pointer_cast(const BasePtr &ptr)
//Correct cast with dynamic_pointer_cast //Correct cast with dynamic_pointer_cast
boost::get_pointer(boost::dynamic_pointer_cast<derived>(ptr)) == boost::get_pointer(boost::dynamic_pointer_cast<derived>(ptr)) ==
//Correct cast with dynamic_cast //Correct cast with dynamic_cast
dynamic_cast<derived*>(boost::get_pointer(ptr)) dynamic_cast<derived*>(boost::get_pointer(ptr))
&& &&
//Incorrect cast with dynamic_pointer_cast //Incorrect cast with dynamic_pointer_cast
boost::get_pointer(boost::dynamic_pointer_cast<derived_derived>(ptr)) == boost::get_pointer(boost::dynamic_pointer_cast<derived_derived>(ptr)) ==
@ -91,7 +107,7 @@ bool check_static_pointer_cast(const BasePtr &ptr)
template <class BasePtr> template <class BasePtr>
bool check_const_pointer_cast(const BasePtr &ptr) bool check_const_pointer_cast(const BasePtr &ptr)
{ {
return return
//Unconst and const again using const_pointer_cast //Unconst and const again using const_pointer_cast
boost::get_pointer( boost::get_pointer(
boost::const_pointer_cast<const base> boost::const_pointer_cast<const base>
@ -101,7 +117,7 @@ bool check_const_pointer_cast(const BasePtr &ptr)
} }
template <class BasePtr> template <class BasePtr>
void check_all_casts(const BasePtr &ptr) void check_all_copy_casts(const BasePtr &ptr)
{ {
#if !defined( BOOST_NO_RTTI ) #if !defined( BOOST_NO_RTTI )
BOOST_TEST( check_dynamic_pointer_cast( ptr ) ); BOOST_TEST( check_dynamic_pointer_cast( ptr ) );
@ -110,15 +126,99 @@ void check_all_casts(const BasePtr &ptr)
BOOST_TEST( check_const_pointer_cast( ptr ) ); BOOST_TEST( check_const_pointer_cast( ptr ) );
} }
#if !defined( BOOST_NO_RTTI )
template <class BasePtr>
bool check_dynamic_moving_pointer_cast(std::function<BasePtr()> f)
{
BasePtr smart1 = f(), smart2 = f();
derived* expect1 = dynamic_cast<derived*>(boost::get_pointer(smart1));
derived_derived* expect2 = dynamic_cast<derived_derived*>(boost::get_pointer(smart2));
//Check that dynamic_pointer_cast versus dynamic_cast
return
//Correct cast with dynamic_pointer_cast
boost::get_pointer(boost::dynamic_pointer_cast<derived>( std::move(smart1) )) == expect1
&&
//Incorrect cast with dynamic_pointer_cast
boost::get_pointer(boost::dynamic_pointer_cast<derived_derived>( std::move(smart2) )) == expect2;
}
#endif
template <class BasePtr>
bool check_static_moving_pointer_cast(std::function<BasePtr()> f)
{
BasePtr smart = f();
base2 *expect = static_cast<base2*>(static_cast<derived*>(boost::get_pointer(smart)));
return
//Cast base -> derived -> base2 using static_pointer_cast
boost::get_pointer(
boost::static_pointer_cast<base2>(
boost::static_pointer_cast<derived>( std::move(smart) ))) ==
//Now the same with static_cast
expect;
}
template <class BasePtr>
bool check_const_moving_pointer_cast(std::function<BasePtr()> f)
{
BasePtr smart = f();
const base *expect = const_cast<const base*>(const_cast<base*>(boost::get_pointer(smart)));
return
//Unconst and const again using const_pointer_cast
boost::get_pointer(
boost::const_pointer_cast<const base>
(boost::const_pointer_cast<base>( std::move(smart) ))) ==
//Now the same with const_cast
expect;
}
template <class BasePtr>
void check_all_moving_casts(std::function<BasePtr()> f) {
#if !defined( BOOST_NO_RTTI )
BOOST_TEST( check_dynamic_moving_pointer_cast( f ) );
#endif
BOOST_TEST( check_static_moving_pointer_cast( f ) );
BOOST_TEST( check_const_moving_pointer_cast( f ) );
}
} }
int main() int main()
{ {
#if !defined( BOOST_NO_CXX11_SMART_PTR )
std::shared_ptr<base> std_shared(new derived); std::shared_ptr<base> std_shared(new derived);
check_all_casts(std_shared); boost::shared_ptr<base> boost_shared(new derived);
#endif base *plain = boost_shared.get();
return boost::report_errors(); // plain & boost::shared_ptr moving pointer_cast checks; there
// is no specific handleing for those types at the moment; this
// test just makes sure they won't break when std::move() is used
// in generic code
check_all_moving_casts<boost::shared_ptr<base>>([&boost_shared]() {
return boost_shared;
});
check_all_moving_casts<base*>([plain]() {
return plain;
});
// std::shared_ptr casts
check_all_copy_casts(std_shared);
check_all_moving_casts<std::shared_ptr<base>>([&std_shared]() {
return std_shared;
});
// std::unique_ptr casts
check_all_moving_casts<std::unique_ptr<base>>([]() {
return std::unique_ptr<base>(new derived);
});
return boost::report_errors();
} }
#endif