it-swarm.com.de

Dynamisches Casting für unique_ptr

Wie in Boost bietet C++ 11 einige Funktionen für das Casting von shared_ptr:

std::static_pointer_cast
std::dynamic_pointer_cast
std::const_pointer_cast

Ich frage mich jedoch, warum es keine Äquivalenzfunktionen für unique_ptr gibt.

Betrachten Sie das folgende einfache Beispiel:

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));

Gibt es einen Grund, warum von diesem Verwendungsmuster abgeraten wird und daher äquivalente Funktionen zu den in shared_ptr vorhandenen Funktionen für unique_ptr nicht vorgesehen sind?

47
betabandido

Die Funktionen, auf die Sie sich beziehen, erzeugen jeweils eine Kopie des Zeigers. Da Sie keine unique_ptr-Kopie erstellen können, ist es nicht sinnvoll, diese Funktionen dafür bereitzustellen.

32
Mark Ransom

Zusätzlich zu Mark Ransoms answer kann ein unique_ptr<X, D> nicht einmal einen X* speichern.

Wenn der Deleter den Typ D::pointer definiert, wird dies gespeichert, und das ist möglicherweise kein echter Zeiger. Er muss nur die NullablePointer-Anforderungen erfüllen und (wenn unique_ptr<X,D>::get() aufgerufen wird) einen operator* haben, der X& zurückgibt, aber nicht erforderlich ist Casting auf andere Arten unterstützen.

unique_ptr ist ziemlich flexibel und verhält sich nicht unbedingt wie ein integrierter Zeigertyp.

Wie angefordert, hier ein Beispiel, bei dem der gespeicherte Typ kein Zeiger ist und daher kein Casting möglich ist. Es ist ein wenig erfunden, umschließt jedoch eine erstellte Datenbank-API (definiert als C-API) in eine C++ - API im RAII-Stil. Der OpaqueDbHandle-Typ erfüllt die NullablePointer-Anforderungen, speichert jedoch nur eine Ganzzahl, die als Schlüssel zum Nachschlagen der tatsächlichen DB-Verbindung über ein implementierungsdefiniertes Mapping verwendet wird. Ich zeige dies nicht als Beispiel für großartiges Design, nur als Beispiel für die Verwendung von unique_ptr zum Verwalten einer nicht kopierbaren, beweglichen Ressource, die kein dynamisch zugewiesener Zeiger ist, wobei der "Deleter" nicht nur einen Destruktor nennt und Speicher freigeben, wenn der unique_ptr den Gültigkeitsbereich verlässt.

#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

Um auf Daves Antwort aufzubauen, versucht diese Vorlagenfunktion, den Inhalt eines unique_ptr in einen anderen eines anderen Typs zu verschieben.

  • Wenn es true zurückgibt, dann entweder:
    • Der Quellzeiger war leer. Der Zielzeiger wird gelöscht, um der semantischen Anforderung zu entsprechen: "Verschieben Sie den Inhalt dieses Zeigers (nichts) in diesen."
    • Das Objekt, auf das der Quellzeiger zeigt, konnte in den Zielzeigertyp konvertiert werden. Der Quellzeiger ist leer und der Zielzeiger zeigt auf dasselbe Objekt, auf das er gezeigt hat. Der Zielzeiger empfängt den Deleter des Quellzeigers (nur bei der ersten Überladung).
  • Wenn false zurückgegeben wird, war der Vorgang nicht erfolgreich. Keiner der Zeiger hat den Status geändert.

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;
}

Beachten Sie, dass die zweite Überladung für Zeiger erforderlich ist, die std::unique_ptr<A> und std::unique_ptr<B> deklariert sind. Die erste Funktion funktioniert nicht, da der erste Zeiger tatsächlich vom Typ std::unique_ptr<A, default_delete<A> > und der zweite von std::unique_ptr<A, default_delete<B> > ist. Die Deleter-Typen sind nicht kompatibel und der Compiler erlaubt Ihnen daher nicht, diese Funktion zu verwenden.

12
cdhowie

Dies ist keine Antwort auf warum , aber es ist eine Möglichkeit, dies zu tun ...

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

Es ist nicht völlig sauber, da 2 unique_ptrs für einen kurzen Moment denken, dass sie das gleiche Objekt besitzen. Wie bereits erwähnt, müssen Sie auch einen benutzerdefinierten Deleter verschieben, wenn Sie einen verwenden (dies ist jedoch sehr selten).

5
David

Wie wäre es damit für einen C++ 11-Ansatz:

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

Wenn Sie den Downcast-Zeiger nur in einem kleinen Bereich verwenden möchten, besteht eine Alternative darin, die reference auf das Objekt, das von unique_ptr verwaltet wird, einfach herunterzuschalten:

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

Ich mochte die Antwort von cdhowie ... aber ich wollte, dass sie return anstelle von out-args verwenden. Folgendes habe ich mir ausgedacht:

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;
}

Ich habe es hier in ein GitHub-Repo gestellt: https://github.com/friedmud/unique_ptr_cast

0
friedmud