54

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か?

4

9 に答える 9

40

Mark Ransom のanswerに加えて、 aunique_ptr<X, D>は an を保存することさえできないかもしれませんX*

デリータがタイプを定義する場合、D::pointerそれが格納され、それは実際のポインタではない可能性があります。NullablePointer要件を満たすだけでよく、(unique_ptr<X,D>::get()が呼び出された場合)operator*を返すX&が必要ですが、他のへのキャストをサポートする必要はありません。種類。

unique_ptr非常に柔軟で、必ずしも組み込みポインター型のように振る舞うとは限りません。

リクエストに応じて、格納された型がポインターではないため、キャストができない例を次に示します。これは少し不自然ですが、(C スタイル API として定義された) 作成されたデータベース API を C++ RAII スタイル API でラップします。OpaqueDbHandle 型はNullablePointer要件を満たしていますが、実装定義のマッピングを介して実際の DB 接続を検索するためのキーとして使用される整数のみを格納します。私はこれを素晴らしいデザインの例として示しているのunique_ptrではなく、動的に割り当てられたポインターではない、コピー不可能で移動可能なリソースを管理するために使用する例として、「deleter」がデストラクタを呼び出すだけでなく、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);
}
于 2012-06-12T18:51:26.330 に答える
34

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

于 2012-06-12T18:40:03.097 に答える
14

デイブの答えに基づいて構築するために、このテンプレート関数は、あるコンテンツ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;
}

2 番目のオーバーロードは、宣言されたポインターstd::unique_ptr<A>std::unique_ptr<B>. 最初のポインタは実際には;の型std::unique_ptr<A, default_delete<A> >で 2 番目のポインタになるため、最初の関数は機能しません。std::unique_ptr<A, default_delete<B> >デリータの型には互換性がないため、コンパイラはこの関数の使用を許可しません。

于 2012-06-12T19:01:57.503 に答える
6

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

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

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

于 2012-06-12T18:41:56.297 に答える
4

C++11 のアプローチでは次のようになります。

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);
}
于 2013-02-08T16:49:15.030 に答える
3

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

auto derived = dynamic_cast<Derived&>(*pBase);
derived.foo();
于 2016-08-15T01:35:05.980 に答える