Merge branch 'karo/unique_ptr_casts' of https://github.com/koraa/smart_ptr into feature/unique_ptr_casts

This commit is contained in:
Peter Dimov
2016-09-10 14:48:26 +03:00
6 changed files with 393 additions and 27 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
@@ -42,4 +44,80 @@ inline T* reinterpret_pointer_cast(U *ptr)
} // namespace boost } // namespace boost
#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>
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
using std::static_pointer_cast;
//dynamic_pointer_cast overload for std::shared_ptr
using std::dynamic_pointer_cast;
//const_pointer_cast overload for std::shared_ptr
using std::const_pointer_cast;
//reinterpret_pointer_cast overload for std::shared_ptr
template<class T, class U> std::shared_ptr<T> reinterpret_pointer_cast(const std::shared_ptr<U> & r ) BOOST_NOEXCEPT
{
(void) reinterpret_cast< T* >( static_cast< U* >( 0 ) );
typedef typename std::shared_ptr<T>::element_type E;
E * p = reinterpret_cast< E* >( r.get() );
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
#endif // #if !defined( BOOST_NO_CXX11_SMART_PTR )
#endif //BOOST_POINTER_CAST_HPP #endif //BOOST_POINTER_CAST_HPP

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. 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. 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>
@@ -47,11 +47,82 @@ template&lt;class T, class U&gt;
inline T* reinterpret_pointer_cast(U *ptr) inline T* reinterpret_pointer_cast(U *ptr)
{ return reinterpret_cast&lt;T*&gt;(ptr); } { return reinterpret_cast&lt;T*&gt;(ptr); }
template&lt;class T, class U&gt;
inline std::shared_ptr&lt;U&gt; static_pointer_cast(std::shared_ptr&lt;T&gt; ptr);
template&lt;class T, class U&gt;
inline std::shared_ptr&lt;U&gt; dynamic_pointer_cast(std::shared_ptr&lt;T&gt; ptr);
template&lt;class T, class U&gt;
inline std::shared_ptr&lt;U&gt; const_pointer_cast(std::shared_ptr&lt;T&gt; ptr);
template&lt;class T, class U&gt;
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

@@ -222,7 +222,7 @@ void bad()
shared_ptr&lt;T&gt; <a href="#dynamic_pointer_cast" >dynamic_pointer_cast</a>(shared_ptr&lt;U&gt; const &amp; r); // never throws shared_ptr&lt;T&gt; <a href="#dynamic_pointer_cast" >dynamic_pointer_cast</a>(shared_ptr&lt;U&gt; const &amp; r); // never throws
template&lt;class T, class U&gt; template&lt;class T, class U&gt;
shared_ptr&lt;T&gt; <a href="#reinterpret_pointer_cast" >reinterpet_pointer_cast</a>(shared_ptr&lt;U&gt; const &amp; r); // never throws shared_ptr&lt;T&gt; <a href="#reinterpret_pointer_cast" >reinterpret_pointer_cast</a>(shared_ptr&lt;U&gt; const &amp; r); // never throws
template&lt;class E, class T, class Y&gt; template&lt;class E, class T, class Y&gt;
std::basic_ostream&lt;E, T&gt; &amp; <a href="#insertion-operator" >operator&lt;&lt;</a> (std::basic_ostream&lt;E, T&gt; &amp; os, shared_ptr&lt;Y&gt; const &amp; p); std::basic_ostream&lt;E, T&gt; &amp; <a href="#insertion-operator" >operator&lt;&lt;</a> (std::basic_ostream&lt;E, T&gt; &amp; os, shared_ptr&lt;Y&gt; const &amp; p);

View File

@@ -29,6 +29,7 @@ import testing ;
[ compile-fail shared_ptr_compare_fail.cpp ] [ compile-fail shared_ptr_compare_fail.cpp ]
[ run shared_ptr_alloc2_test.cpp ] [ run shared_ptr_alloc2_test.cpp ]
[ run pointer_cast_test.cpp ] [ run pointer_cast_test.cpp ]
[ run cpp11_pointer_cast_test.cpp ]
[ compile pointer_to_other_test.cpp ] [ compile pointer_to_other_test.cpp ]
[ run auto_ptr_rv_test.cpp ] [ run auto_ptr_rv_test.cpp ]
[ run shared_ptr_alias_test.cpp ] [ run shared_ptr_alias_test.cpp ]

View File

@@ -0,0 +1,224 @@
//
// 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
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/pointer_cast.hpp>
#include <boost/config.hpp>
#include <boost/detail/lightweight_test.hpp>
#include <boost/get_pointer.hpp>
#include <boost/shared_ptr.hpp>
#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
{
// Let's create these inheritance relationship:
//
// base base2
// | |
// derived
// |
// derived_derived
//
class base
{
public:
virtual ~base(){}
int filler [5];
};
class base2
{
public:
virtual ~base2(){}
int filler [5];
};
class derived
: public base, public base2
{
int filler [5];
};
class derived_derived
: public derived
{
int filler [5];
};
// And now some simple check functions
#if !defined( BOOST_NO_RTTI )
template <class BasePtr>
bool check_dynamic_pointer_cast(const BasePtr &ptr)
{
//Check that dynamic_pointer_cast versus dynamic_cast
return
//Correct cast with dynamic_pointer_cast
boost::get_pointer(boost::dynamic_pointer_cast<derived>(ptr)) ==
//Correct cast with dynamic_cast
dynamic_cast<derived*>(boost::get_pointer(ptr))
&&
//Incorrect cast with dynamic_pointer_cast
boost::get_pointer(boost::dynamic_pointer_cast<derived_derived>(ptr)) ==
//Incorrect cast with dynamic_cast
dynamic_cast<derived_derived*>(boost::get_pointer(ptr));
}
#endif
template <class BasePtr>
bool check_static_pointer_cast(const BasePtr &ptr)
{
return
//Cast base -> derived -> base2 using static_pointer_cast
boost::get_pointer(
boost::static_pointer_cast<base2>(
boost::static_pointer_cast<derived>(ptr))) ==
//Now the same with static_cast
static_cast<base2*>(static_cast<derived*>(boost::get_pointer(ptr)));
}
template <class BasePtr>
bool check_const_pointer_cast(const BasePtr &ptr)
{
return
//Unconst and const again using const_pointer_cast
boost::get_pointer(
boost::const_pointer_cast<const base>
(boost::const_pointer_cast<base>(ptr))) ==
//Now the same with const_cast
const_cast<const base*>(const_cast<base*>(boost::get_pointer(ptr)));
}
template <class BasePtr>
void check_all_copy_casts(const BasePtr &ptr)
{
#if !defined( BOOST_NO_RTTI )
BOOST_TEST( check_dynamic_pointer_cast( ptr ) );
#endif
BOOST_TEST( check_static_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()
{
std::shared_ptr<base> std_shared(new derived);
boost::shared_ptr<base> boost_shared(new derived);
base *plain = boost_shared.get();
// 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

View File

@@ -104,15 +104,9 @@ bool check_const_pointer_cast(const BasePtr &ptr)
const_cast<const base*>(const_cast<base*>(boost::get_pointer(ptr))); const_cast<const base*>(const_cast<base*>(boost::get_pointer(ptr)));
} }
} template <class BasePtr>
void check_all_casts(const BasePtr &ptr)
int main()
{ {
{
// Try casts with shared_ptr
boost::shared_ptr<base> ptr(new derived);
#if !defined( BOOST_NO_RTTI ) #if !defined( BOOST_NO_RTTI )
BOOST_TEST( check_dynamic_pointer_cast( ptr ) ); BOOST_TEST( check_dynamic_pointer_cast( ptr ) );
#endif #endif
@@ -120,17 +114,15 @@ int main()
BOOST_TEST( check_const_pointer_cast( ptr ) ); BOOST_TEST( check_const_pointer_cast( ptr ) );
} }
{
// Try casts with raw pointer
boost::scoped_ptr<base> ptr(new derived);
#if !defined( BOOST_NO_RTTI )
BOOST_TEST( check_dynamic_pointer_cast( ptr.get() ) );
#endif
BOOST_TEST( check_static_pointer_cast( ptr.get() ) );
BOOST_TEST( check_const_pointer_cast( ptr.get() ) );
} }
int main()
{
boost::shared_ptr<base> boost_shared(new derived);
base *plain = boost_shared.get();
check_all_casts(boost_shared);
check_all_casts(plain);
return boost::report_errors(); return boost::report_errors();
} }