diff --git a/include/boost/pointer_cast.hpp b/include/boost/pointer_cast.hpp index 3c65cac..6a5af8b 100644 --- a/include/boost/pointer_cast.hpp +++ b/include/boost/pointer_cast.hpp @@ -7,6 +7,8 @@ // ////////////////////////////////////////////////////////////////////////////// +#include + #ifndef 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 ) +#include + +#include +#include +#include + #include namespace boost { +namespace detail { + +template +void assert_safe_moving_upcast() { + BOOST_STATIC_ASSERT_MSG( !(boost::is_base_of::value && !boost::is_pod::value && !boost::has_virtual_destructor::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; @@ -68,6 +87,35 @@ template std::shared_ptr reinterpret_pointer_cast(const std return std::shared_ptr( r, p ); } +//static_pointer_cast overload for std::unique_ptr +template std::unique_ptr static_pointer_cast( std::unique_ptr && r ) BOOST_NOEXCEPT +{ + detail::assert_safe_moving_upcast(); + return std::unique_ptr( static_cast( r.release() ) ); +} + +//dynamic_pointer_cast overload for std::unique_ptr +template std::unique_ptr dynamic_pointer_cast( std::unique_ptr && r ) BOOST_NOEXCEPT +{ + detail::assert_safe_moving_upcast(); + + T * p = dynamic_cast( r.get() ); + if( p ) r.release(); + return std::unique_ptr( p ); +} + +//const_pointer_cast overload for std::unique_ptr +template std::unique_ptr const_pointer_cast( std::unique_ptr && r ) BOOST_NOEXCEPT +{ + return std::unique_ptr( const_cast( r.release() ) ); +} + +//reinterpret_pointer_cast overload for std::unique_ptr +template std::unique_ptr reinterpret_pointer_cast( std::unique_ptr && r ) BOOST_NOEXCEPT +{ + return std::unique_ptr( reinterpret_cast( r.release() ) ); +} + } // namespace boost #endif // #if !defined( BOOST_NO_CXX11_SMART_PTR ) diff --git a/pointer_cast.html b/pointer_cast.html index 4e52cf0..f0c7fa2 100644 --- a/pointer_cast.html +++ b/pointer_cast.html @@ -9,7 +9,7 @@ width="277" align="middle" border="0">pointer_cast

The pointer cast functions (boost::static_pointer_cast boost::dynamic_pointer_cast boost::reinterpret_pointer_cast boost::const_pointer_cast) - 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 boost/pointer_cast.hpp.

There is test/example code in pointer_cast_test.cpp.

Rationale

@@ -23,7 +23,7 @@ template<class T, class U>

Pointer cast functions from boost/pointer_cast.hpp are overloads of boost::static_pointer_cast, boost::dynamic_pointer_cast, boost::reinterpret_pointer_cast and boost::const_pointer_cast - 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 can be used for raw and smart pointers.

Synopsis

@@ -58,12 +58,71 @@ inline std::shared_ptr<U> const_pointer_cast(std::shared_ptr<T> ptr) template<class T, class U> inline std::shared_ptr<U> reinterpret_pointer_cast(std::shared_ptr<T> ptr); + +template<class T, class U> +inline std::unique_ptr<U> static_pointer_cast(std::unique_ptr<T> &&ptr); + +template<class T, class U> +inline std::unique_ptr<U> dynamic_pointer_cast(std::unique_ptr<T> &&ptr); + +template<class T, class U> +inline std::unique_ptr<U> const_pointer_cast(std::unique_ptr<T> &&ptr); + +template<class T, class U> +inline std::unique_ptr<U> reinterpret_pointer_cast(std::unique_ptr<T> &&ptr); } // namespace boost -

As you can see from the above synopsis, the pointer cast functions are just - wrappers around standard C++ cast operators.

+

As you can see from the above synopsis, the pointer cast functions for raw pointers are just + wrappers around standard C++ cast operators.

+ +

Memory Safety

+

It is possible to write unsafe code, when upcasting to a base type without virtual destructor. + Consider the following example:

+
+#include <memory>
+#include <utility>
+#include <boost/pointer_cast.hpp>
+#include <boost/make_unique.hpp>
+
+int destructed = 0;
+
+struct base {
+  ~base() {
+    // ...
+  }
+};
+
+struct child : base {
+  virtual ~child() {
+    destructed++;
+  }
+}
+
+int main() {
+  {
+    std::unique_ptr tmp = boost::make_unique();
+    std::unique_ptr sink = boost::static_pointer_cast( std::move(tmp) );
+  }
+
+  // child::~child was never called
+  assert(destructed == 0);
+
+  return 0;
+}
+
+

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.

+

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.

+

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.

+

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.

+

Example

diff --git a/test/cpp11_pointer_cast_test.cpp b/test/cpp11_pointer_cast_test.cpp
index a937708..a9361c3 100644
--- a/test/cpp11_pointer_cast_test.cpp
+++ b/test/cpp11_pointer_cast_test.cpp
@@ -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
 //
@@ -8,12 +8,28 @@
 // http://www.boost.org/LICENSE_1_0.txt)
 //
 
-#include 
 #include 
+
+#include 
 #include 
 #include 
+#include 
 
 #include 
+#include 
+#include 
+
+#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
 {
@@ -43,7 +59,7 @@ public:
 };
 
 class derived
-   : public base, public base2 
+   : public base, public base2
 {
     int filler [5];
 };
@@ -66,7 +82,7 @@ bool check_dynamic_pointer_cast(const BasePtr &ptr)
    //Correct cast with dynamic_pointer_cast
    boost::get_pointer(boost::dynamic_pointer_cast(ptr)) ==
       //Correct cast with dynamic_cast
-      dynamic_cast(boost::get_pointer(ptr)) 
+      dynamic_cast(boost::get_pointer(ptr))
    &&
    //Incorrect cast with dynamic_pointer_cast
    boost::get_pointer(boost::dynamic_pointer_cast(ptr)) ==
@@ -91,7 +107,7 @@ bool check_static_pointer_cast(const BasePtr &ptr)
 template 
 bool check_const_pointer_cast(const BasePtr &ptr)
 {
-   return   
+   return
    //Unconst and const again using const_pointer_cast
    boost::get_pointer(
       boost::const_pointer_cast
@@ -101,7 +117,7 @@ bool check_const_pointer_cast(const BasePtr &ptr)
 }
 
 template 
-void check_all_casts(const BasePtr &ptr)
+void check_all_copy_casts(const BasePtr &ptr)
 {
 #if !defined( BOOST_NO_RTTI )
    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 ) );
 }
 
+
+#if !defined( BOOST_NO_RTTI )
+
+template 
+bool check_dynamic_moving_pointer_cast(std::function f)
+{
+   BasePtr smart1 = f(), smart2 = f();
+   derived* expect1 = dynamic_cast(boost::get_pointer(smart1));
+   derived_derived* expect2 = dynamic_cast(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( std::move(smart1) )) == expect1
+   &&
+   //Incorrect cast with dynamic_pointer_cast
+   boost::get_pointer(boost::dynamic_pointer_cast( std::move(smart2) )) == expect2;
+}
+
+#endif
+
+template 
+bool check_static_moving_pointer_cast(std::function f)
+{
+   BasePtr smart = f();
+   base2 *expect = static_cast(static_cast(boost::get_pointer(smart)));
+
+   return
+   //Cast base -> derived -> base2 using static_pointer_cast
+   boost::get_pointer(
+            boost::static_pointer_cast(
+               boost::static_pointer_cast( std::move(smart) ))) ==
+   //Now the same with static_cast
+   expect;
+}
+
+template 
+bool check_const_moving_pointer_cast(std::function f)
+{
+   BasePtr smart = f();
+   const base *expect = const_cast(const_cast(boost::get_pointer(smart)));
+   return
+   //Unconst and const again using const_pointer_cast
+   boost::get_pointer(
+      boost::const_pointer_cast
+         (boost::const_pointer_cast( std::move(smart) ))) ==
+   //Now the same with const_cast
+   expect;
+}
+
+template 
+void check_all_moving_casts(std::function 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()
 {
-#if !defined( BOOST_NO_CXX11_SMART_PTR )
+
    std::shared_ptr std_shared(new derived);
-   check_all_casts(std_shared);
-#endif
+   boost::shared_ptr boost_shared(new derived);
+   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]() {
+       return boost_shared;
+   });
+
+   check_all_moving_casts([plain]() {
+       return plain;
+   });
+
+   // std::shared_ptr casts
+
+   check_all_copy_casts(std_shared);
+   check_all_moving_casts>([&std_shared]() {
+       return std_shared;
+   });
+
+   // std::unique_ptr casts
+
+   check_all_moving_casts>([]() {
+       return std::unique_ptr(new derived);
+   });
+
+    return boost::report_errors();
 }
-
+#endif