Boostの場合と同様に、C++ 11はshared_ptr
をキャストするためのいくつかの関数を提供します。
std::static_pointer_cast
std::dynamic_pointer_cast
std::const_pointer_cast
ただし、unique_ptr
に相当する関数がないのはなぜかと思います。
次の簡単な例を考えてみましょう。
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));
この使用パターンが推奨されない理由はありますか?したがって、shared_ptr
に存在するものと同等の機能はunique_ptr
には提供されませんか?
それぞれ参照する関数は、ポインターのcopyを作成します。 unique_ptr
のコピーを作成できないため、これらの機能を提供することは意味がありません。
Mark Ransomの answer に加えて、unique_ptr<X, D>
はX*
を保存することすらできないかもしれません。
削除者がD::pointer
型を定義している場合、それが保存され、実際のポインターではない可能性があります。それは、NullablePointer
要件を満たす必要があり、(unique_ptr<X,D>::get()
が呼び出された場合)operator*
を返すX&
がありますが、他の型へのキャストをサポートする必要はありません。
unique_ptr
は非常に柔軟であり、必ずしも組み込みのポインター型のように動作するわけではありません。
要求されたとおり、ここに格納された型がポインタではないため、キャストができない例があります。少し工夫されていますが、C++ RAIIスタイルのAPIで作成されたデータベースAPI(CスタイルのAPIとして定義されています)をラップしています。 OpaqueDbHandle型はNullablePointer
要件を満たしますが、整数のみを格納します。整数は、実装定義のマッピングを介して実際のDB接続を検索するためのキーとして使用されます。 unique_ptr
を使用して、動的に割り当てられたポインターではない、コピー不可の移動可能なリソースを管理する例として、これを優れたデザインの例として示していません。 unique_ptr
が範囲外になったときにデストラクタを呼び出してメモリの割り当てを解除するだけです。
#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);
}
Daveの答えに基づいて、このテンプレート関数は、あるunique_ptr
のコンテンツを別のタイプの別のコンテンツに移動しようとします。
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;
}
std::unique_ptr<A>
およびstd::unique_ptr<B>
として宣言されたポインターには、2番目のオーバーロードが必要であることに注意してください。最初のポインターは実際にはstd::unique_ptr<A, default_delete<A> >
型であり、2番目のポインターはstd::unique_ptr<A, default_delete<B> >
;であるため、最初の関数は機能しません。削除タイプは互換性がないため、コンパイラはこの関数の使用を許可しません。
これはwhyに対する答えではありませんが、それを行う方法です...
std::unique_ptr<A> x(new B);
std::unique_ptr<B> y(dynamic_cast<B*>(x.get()));
if(y)
x.release();
少しの間2 unique_ptr
sは、同じオブジェクトを所有していると考えています。また、コメントされたように、カスタムデリートを使用する場合は、カスタムデリートの移動を管理する必要があります(ただし、それは非常にまれです)。
C++ 11アプローチの場合はどうですか:
template <class T_SRC, class T_DEST>
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> ret(dest_ptr);
}
小さいスコープでダウンキャストポインターのみを使用する場合、1つの代替方法は、referenceをunique_ptr
で管理されているオブジェクトに単純にダウンキャストすることです。
auto derived = dynamic_cast<Derived&>(*pBase);
derived.foo();
私はcdhowieの答えが好きでした...しかし、私はそれらにout-argsを使用する代わりにreturnを望んでいました。ここに私が思いついたものがあります:
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;
}
ここでGitHubリポジトリに入れました: https://github.com/friedmud/unique_ptr_cast