web-dev-qa-db-fra.com

Diffusion dynamique pour unique_ptr

Comme ce fut le cas dans Boost, C++ 11 fournit certaines fonctions pour transtyper shared_ptr:

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

Je me demande cependant pourquoi il n’existe pas de fonctions équivalentes pour unique_ptr.

Prenons l'exemple simple suivant:

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

Y a-t-il une raison pour laquelle ce modèle d'utilisation est déconseillé et que, par conséquent, les fonctions équivalentes à celles présentes dans shared_ptr ne sont pas fournies pour unique_ptr?

47
betabandido

Les fonctions auxquelles vous faites référence créent un copier du pointeur. Étant donné que vous ne pouvez pas copier un unique_ptr, il n’a aucun sens de lui fournir ces fonctions.

32
Mark Ransom

En plus de answer de Mark Ransom, un unique_ptr<X, D> pourrait même ne pas stocker un X*.

Si le fichier de suppression définit le type D::pointer, c'est ce qui est stocké et ce n'est peut-être pas un véritable pointeur. Il doit seulement répondre aux exigences NullablePointer et (si unique_ptr<X,D>::get() est appelé) avoir un operator* qui renvoie X&, mais ce n'est pas obligatoire pour soutenir le casting à d'autres types.

unique_ptr est assez flexible et ne se comporte pas nécessairement comme un type de pointeur intégré.

Comme demandé, voici un exemple où le type stocké n'est pas un pointeur et par conséquent, la conversion n'est pas possible. C'est un peu artificiel, mais englobe une API de base de données composée (définie comme une API de style C) dans une API de style C++ RAII. Le type OpaqueDbHandle répond aux exigences NullablePointer, mais ne stocke qu'un entier, qui est utilisé comme clé pour rechercher la connexion à la base de données réelle via un mappage défini par l'implémentation. Je ne montre pas cela comme un exemple de bon design, mais simplement comme exemple d'utilisation de unique_ptr pour gérer une ressource déplaçable non copiable qui n'est pas un pointeur alloué de manière dynamique, où le "deleter" n'appelle pas simplement un destructeur et libérez de la mémoire lorsque le unique_ptr sort de la portée.

#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

Pour compléter la réponse de Dave, cette fonction de modèle tentera de déplacer le contenu d'un unique_ptr vers un autre d'un type différent.

  • Si cela retourne vrai, alors soit:
    • Le pointeur source était vide. Le pointeur de destination sera effacé pour répondre à la demande sémantique de "déplacer le contenu de ce pointeur (rien) dans celui-ci".
    • L'objet pointé par le pointeur source était convertible en type de pointeur de destination. Le pointeur source sera vide et le pointeur de destination pointera sur le même objet que celui utilisé auparavant. Le pointeur de destination recevra le suppresseur du pointeur source (uniquement lors de la première surcharge).
  • S'il renvoie false, l'opération a échoué. Aucun pointeur n'aura changé d'état.

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

Notez que la deuxième surcharge est requise pour les pointeurs déclarés std::unique_ptr<A> et std::unique_ptr<B>. La première fonction ne fonctionnera pas car le premier pointeur sera de type std::unique_ptr<A, default_delete<A> > et le second de std::unique_ptr<A, default_delete<B> >; les types de suppression ne seront pas compatibles et le compilateur ne vous autorisera pas à utiliser cette fonction.

12
cdhowie

Ce n'est pas une réponse à pourquoi , mais c'est une façon de le faire ...

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

Ce n'est pas tout à fait propre car pendant un bref instant 2 unique_ptrs pensent qu'ils possèdent le même objet. Et comme cela a été commenté, vous devrez également gérer le déplacement d'un suppresseur personnalisé si vous en utilisez un (mais c'est très rare).

5
David

Que diriez-vous de cela pour une approche 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

Si vous n'utilisez que le pointeur downcast dans une petite portée, une alternative consiste simplement à downcast le reference à l'objet géré par le unique_ptr:

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

J'ai bien aimé la réponse de cdhowie ... mais je voulais qu'ils retournent au lieu d'utiliser des arguments extrajudiciaires. Voici ce que je suis venu avec:

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

Je l'ai mis dans un dépôt GitHub ici: https://github.com/friedmud/unique_ptr_cast

0
friedmud