web-dev-qa-db-ja.com

unique_ptrの動的キャスト

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には提供されませんか?

48
betabandido

それぞれ参照する関数は、ポインターのcopyを作成します。 unique_ptrのコピーを作成できないため、これらの機能を提供することは意味がありません。

32
Mark Ransom

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);
}
38
Jonathan Wakely

Daveの答えに基づいて、このテンプレート関数は、あるunique_ptrのコンテンツを別のタイプの別のコンテンツに移動しようとします。

  • Trueを返す場合、次のいずれかです。
    • ソースポインターが空でした。宛先ポインターは、「このポインターの内容(何も)をそのポインターに移動する」というセマンティックリクエストに準拠するためにクリアされます。
    • ソースポインターが指すオブジェクトは、宛先ポインタータイプに変換可能でした。ソースポインタは空になり、デスティネーションポインタは、指すのに使用したのと同じオブジェクトを指します。宛先ポインターは、ソースポインターの削除機能を受け取ります(最初のオーバーロードを使用する場合のみ)。
  • Falseを返した場合、操作は失敗しています。どちらのポインターも状態を変更しません。

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> >;であるため、最初の関数は機能しません。削除タイプは互換性がないため、コンパイラはこの関数の使用を許可しません。

12
cdhowie

これはwhyに対する答えではありませんが、それを行う方法です...

std::unique_ptr<A> x(new B);
std::unique_ptr<B> y(dynamic_cast<B*>(x.get()));
if(y)
    x.release();

少しの間2 unique_ptrsは、同じオブジェクトを所有していると考えています。また、コメントされたように、カスタムデリートを使用する場合は、カスタムデリートの移動を管理する必要があります(ただし、それは非常にまれです)。

5
David

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);
}
3
Bob F

小さいスコープでダウンキャストポインターのみを使用する場合、1つの代替方法は、referenceunique_ptrで管理されているオブジェクトに単純にダウンキャストすることです。

auto derived = dynamic_cast<Derived&>(*pBase);
derived.foo();
2
Emile Cormier

私は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

0
friedmud