Reputation: 19694
As it was the case in Boost, C++11 provides some functions for casting shared_ptr
:
std::static_pointer_cast
std::dynamic_pointer_cast
std::const_pointer_cast
I am wondering, however, why there are no equivalents functions for unique_ptr
.
Consider the following simple example:
class A { virtual ~A(); ... }
class B : public A { ... }
unique_ptr<A> pA(new B(...));
unique_ptr<A> qA = std::move(pA); // This is legal since there is no casting
unique_ptr<B> pB = std::move(pA); // This is not legal
// I would like to do something like:
// (Of course, it is not valid, but that would be the idea)
unique_ptr<B> pB = std::move(std::dynamic_pointer_cast<B>(pA));
Is there any reason why this usage pattern is discouraged, and thus, equivalent functions to the ones present in shared_ptr
are not provided for unique_ptr
?
Upvotes: 57
Views: 47288
Reputation: 69
How about this for a C++11 approach:
template <class T_SRC, class T_DEST>
inline std::unique_ptr<T_DEST> unique_cast(std::unique_ptr<T_SRC> &&src)
{
if (!src) return std::unique_ptr<T_DEST>();
// Throws a std::bad_cast() if this doesn't work out
T_DEST *dest_ptr = &dynamic_cast<T_DEST &>(*src.get());
src.release();
return std::unique_ptr<T_DEST>(dest_ptr);
}
Upvotes: 4
Reputation: 775
I refined the answer of @Bob F https://stackoverflow.com/a/14777419/1702991 so you only need one template parameter now as usual for other types of cast
template <class destinationT, typename sourceT>
std::unique_ptr<destinationT> unique_cast(std::unique_ptr<sourceT>&& source)
{
if (!source)
return std::unique_ptr<destinationT>();
// Throws a std::bad_cast() if this doesn't work out
destinationT* dest_ptr = &dynamic_cast<destinationT&>(*source.get());
source.release();
return std::unique_ptr<destinationT>(dest_ptr);
}
Update (non throwing version):
template <class destinationT, typename sourceT>
std::unique_ptr<destinationT> unique_cast(std::unique_ptr<sourceT>&& source)
{
if (!source)
return std::unique_ptr<destinationT>();
destinationT* dest_ptr = dynamic_cast<destinationT*>(source.get());
if(dest_ptr)
source.release();
return std::unique_ptr<destinationT>(dest_ptr);
}
Usage:
std::unique_ptr<MyClass> obj = unique_cast<MyClass>(std::make_unique<MyOtherClass>());
Upvotes: 1
Reputation: 2214
With all the samples I've seen so far I have come up with this version.
template <typename T_DEST, typename T_SRC, typename T_DELETER>
std::unique_ptr<T_DEST, T_DELETER>
dynamic_pointer_cast(std::unique_ptr<T_SRC, T_DELETER>&& src)
{
// When nullptr, just return nullptr
if (!src) return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
// Perform dynamic_cast, throws std::bad_cast() if this doesn't work out
T_DEST* dest_ptr = dynamic_cast<T_DEST*>(src.get());
// Do not return nullptr on bad_cast
//if (!dest_ptr) return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
// But throw std::bad_cast instead
if (!dest_ptr) throw std::bad_cast();
// Move into new unique_ptr
std::unique_ptr<T_DEST, T_DELETER> dest_temp(dest_ptr, std::move(src.get_deleter()));
src.release();
return dest_temp;
}
template <typename T_DEST, typename T_SRC>
std::unique_ptr<T_DEST>
dynamic_pointer_cast(std::unique_ptr<T_SRC>&& src)
{
// When nullptr, just return nullptr
if (!src) return std::unique_ptr<T_DEST>(nullptr);
// Perform dynamic_cast, throws std::bad_cast() if this doesn't work out
T_DEST* dest_ptr = dynamic_cast<T_DEST*>(src.get());
// Do not return nullptr on bad_cast
//if (!dest_ptr) return std::unique_ptr<T_DEST>(nullptr);
// But throw std::bad_cast instead
if (!dest_ptr) throw std::bad_cast();
// Move into new unique_ptr
std::unique_ptr<T_DEST> dest_temp(dest_ptr);
src.release();
return dest_temp;
}
If you want to let dynamic_cast throw the std::bad_cast instead, just dynamic_cast to a reference instead
Use it like this
auto src = std::make_unique<Base>();
auto dst = dynamic_pointer_cast<Derived>(std::move(src));
auto dst2 = dynamic_pointer_cast<Derived>(FunctionReturningBase());
Upvotes: 0
Reputation: 673
I liked cdhowie's answer... but I wanted them to return instead of using out-args. Here's what I came up with:
template <typename T_DEST, typename T_SRC, typename T_DELETER>
std::unique_ptr<T_DEST, T_DELETER>
dynamic_pointer_cast(std::unique_ptr<T_SRC, T_DELETER> & src)
{
if (!src)
return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
if (!dest_ptr)
return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
std::unique_ptr<T_DEST, T_DELETER> dest_temp(dest_ptr, std::move(src.get_deleter()));
src.release();
return dest_temp;
}
template <typename T_SRC, typename T_DEST>
std::unique_ptr<T_DEST>
dynamic_pointer_cast(std::unique_ptr<T_SRC> & src)
{
if (!src)
return std::unique_ptr<T_DEST>(nullptr);
T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
if (!dest_ptr)
return std::unique_ptr<T_DEST>(nullptr);
std::unique_ptr<T_DEST> dest_temp(dest_ptr);
src.release();
return dest_temp;
}
I put it into a GitHub repo here: https://github.com/friedmud/unique_ptr_cast
Upvotes: 0
Reputation: 29209
If you're only going the be using the downcast pointer in a small scope, one alternative is to simply downcast the reference to the object being managed by the unique_ptr
:
auto derived = dynamic_cast<Derived&>(*pBase);
derived.foo();
Upvotes: 4
Reputation: 169093
To build on Dave's answer, this template function will attempt to move the contents of one unique_ptr
to another of a different type.
template <typename T_SRC, typename T_DEST, typename T_DELETER>
bool dynamic_pointer_move(std::unique_ptr<T_DEST, T_DELETER> & dest,
std::unique_ptr<T_SRC, T_DELETER> & src) {
if (!src) {
dest.reset();
return true;
}
T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
if (!dest_ptr)
return false;
std::unique_ptr<T_DEST, T_DELETER> dest_temp(
dest_ptr,
std::move(src.get_deleter()));
src.release();
dest.swap(dest_temp);
return true;
}
template <typename T_SRC, typename T_DEST>
bool dynamic_pointer_move(std::unique_ptr<T_DEST> & dest,
std::unique_ptr<T_SRC> & src) {
if (!src) {
dest.reset();
return true;
}
T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
if (!dest_ptr)
return false;
src.release();
dest.reset(dest_ptr);
return true;
}
Note that the second overload is required for pointers declared std::unique_ptr<A>
and std::unique_ptr<B>
. The first function will not work because the first pointer will actually be of type std::unique_ptr<A, default_delete<A> >
and the second of std::unique_ptr<A, default_delete<B> >
; the deleter types won't be compatible and so the compiler will not allow you to use this function.
Upvotes: 14
Reputation: 28178
This isn't an answer to why, but it is a way to do it...
std::unique_ptr<A> x(new B);
std::unique_ptr<B> y(dynamic_cast<B*>(x.get()));
if(y)
x.release();
It's not entirely clean since for a brief moment 2 unique_ptr
s think they own the same object. And as was commented, you'll also have to manage moving a custom deleter if you use one (but that's very rare).
Upvotes: 7
Reputation: 171333
In addition to Mark Ransom's answer, a unique_ptr<X, D>
might not even store an X*
.
If the deleter defines the type D::pointer
then that's what is stored, and that might not be a real pointer, it only needs to meet the NullablePointer
requirements and (if unique_ptr<X,D>::get()
is called) have an operator*
that returns X&
, but it isn't required to support casting to other types.
unique_ptr
is quite flexible and doesn't necessarily behave very much like a built-in pointer type.
As requested, here is an example where the stored type is not a pointer, and therefore casting is not possible. It's a bit contrived, but wraps a made-up database API (defined as a C-style API) in a C++ RAII-style API. The OpaqueDbHandle type meets the NullablePointer
requirements, but only stores an integer, which is used as a key to lookup the actual DB connection via some implementation-defined mapping. I'm not showing this as an example of great design, just as an example of using unique_ptr
to manage a non-copyable, movable resource which is not a dynamically-allocated pointer, where the "deleter" doesn't just call a destructor and deallocate memory when the unique_ptr
goes out of scope.
#include <memory>
// native database API
extern "C"
{
struct Db;
int db_query(Db*, const char*);
Db* db_connect();
void db_disconnect(Db*);
}
// wrapper API
class OpaqueDbHandle
{
public:
explicit OpaqueDbHandle(int id) : id(id) { }
OpaqueDbHandle(std::nullptr_t) { }
OpaqueDbHandle() = default;
OpaqueDbHandle(const OpaqueDbHandle&) = default;
OpaqueDbHandle& operator=(const OpaqueDbHandle&) = default;
OpaqueDbHandle& operator=(std::nullptr_t) { id = -1; return *this; }
Db& operator*() const;
explicit operator bool() const { return id > 0; }
friend bool operator==(const OpaqueDbHandle& l, const OpaqueDbHandle& r)
{ return l.id == r.id; }
private:
friend class DbDeleter;
int id = -1;
};
inline bool operator!=(const OpaqueDbHandle& l, const OpaqueDbHandle& r)
{ return !(l == r); }
struct DbDeleter
{
typedef OpaqueDbHandle pointer;
void operator()(pointer p) const;
};
typedef std::unique_ptr<Db, DbDeleter> safe_db_handle;
safe_db_handle safe_connect();
int main()
{
auto db_handle = safe_connect();
(void) db_query(&*db_handle, "SHOW TABLES");
}
// defined in some shared library
namespace {
std::map<int, Db*> connections; // all active DB connections
std::list<int> unused_connections; // currently unused ones
int next_id = 0;
const unsigned cache_unused_threshold = 10;
}
Db& OpaqueDbHandle::operator*() const
{
return connections[id];
}
safe_db_handle safe_connect()
{
int id;
if (!unused_connections.empty())
{
id = unused_connections.back();
unused_connections.pop_back();
}
else
{
id = next_id++;
connections[id] = db_connect();
}
return safe_db_handle( OpaqueDbHandle(id) );
}
void DbDeleter::operator()(DbDeleter::pointer p) const
{
if (unused_connections.size() >= cache_unused_threshold)
{
db_disconnect(&*p);
connections.erase(p.id);
}
else
unused_connections.push_back(p.id);
}
Upvotes: 42
Reputation: 308216
The functions you refer to each make a copy of the pointer. Since you can't make a copy of a unique_ptr
it doesn't make sense to provide those functions for it.
Upvotes: 36