Casting dynamic pour unique_ptr

Comme ce fut le cas dans Boost, C ++ 11 fournit des fonctions pour le casting de shared_ptr :

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

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

Prenons l’exemple simple suivant:

 class A { virtual ~A(); ... } class B : public A { ... } unique_ptr pA(new B(...)); unique_ptr qA = std::move(pA); // This is legal since there is no casting unique_ptr 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 pB = std::move(std::dynamic_pointer_cast(pA)); 

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

Les fonctions auxquelles vous vous référez font chacune une copie du pointeur. Comme vous ne pouvez pas faire une copie d’un unique_ptr cela n’a aucun sens de lui fournir ces fonctions.

En plus de la réponse de Mark Ransom, un unique_ptr pourrait même ne pas stocker un X* .

Si le déléteur définit le type D::pointer alors c’est ce qui est stocké, et ce n’est peut-être pas un vrai pointeur, il suffit qu’il réponde aux exigences de NullablePointer et (si unique_ptr::get() est appelé) avoir un operator* qui renvoie X& , mais il n’est pas nécessaire de prendre en charge la conversion vers d’autres types.

unique_ptr est assez flexible et ne se comporte pas forcément 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 diffusion n’est pas possible. C’est un peu artificiel, mais encapsule une API de firebase database (définie comme une API de style C) dans une API de style C ++ RAII. Le type OpaqueDbHandle répond aux exigences de NullablePointer , mais ne stocke qu’un entier, qui est utilisé comme clé pour rechercher la connexion à la firebase database réelle via un mappage défini par l’implémentation. Je ne montre pas cela comme un exemple de conception géniale, juste comme exemple d’utilisation de unique_ptr pour gérer une ressource mobile non copiable, qui n’est pas un pointeur alloué dynamicment, où le “deleter” n’appelle pas simplement un destructeur. et désallouer la mémoire lorsque l’ unique_ptr est hors de scope.

 #include  // 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 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 connections; // all active DB connections std::list 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); } 

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

  • Si elle retourne vrai, alors soit:
    • Le pointeur source était vide. Le pointeur de destination sera effacé pour se conformer à la requête sémantique de “déplacer le contenu de ce pointeur (rien) dans celui-ci.”
    • L’object désigné 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 object qu’il utilisait pour pointer. Le pointeur de destination recevra le paramètre deleter du pointeur source (uniquement lors de l’utilisation de la première surcharge).
  • Si elle renvoie false, l’opération a échoué. Aucun pointeur n’aura changé d’état.
 template  bool dynamic_pointer_move(std::unique_ptr & dest, std::unique_ptr & src) { if (!src) { dest.reset(); return true; } T_DEST * dest_ptr = dynamic_cast(src.get()); if (!dest_ptr) return false; std::unique_ptr dest_temp( dest_ptr, std::move(src.get_deleter())); src.release(); dest.swap(dest_temp); return true; } template  bool dynamic_pointer_move(std::unique_ptr & dest, std::unique_ptr & src) { if (!src) { dest.reset(); return true; } T_DEST * dest_ptr = dynamic_cast(src.get()); if (!dest_ptr) return false; src.release(); dest.reset(dest_ptr); return true; } 

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

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

 std::unique_ptr x(new B); std::unique_ptr y(dynamic_cast(x.get())); if(y) x.release(); 

Ce n’est pas tout à fait propre puisque pendant un bref instant 2 unique_ptr pense posséder le même object. Et comme cela a été commenté, vous devrez également gérer le déplacement d’un deleter personnalisé si vous en utilisez un (mais c’est très rare).

Que diriez-vous de cela pour une approche C ++ 11:

 template  std::unique_ptr unique_cast(std::unique_ptr &&src) { if (!src) return std::unique_ptr(); // Throws a std::bad_cast() if this doesn't work out T_DEST *dest_ptr = &dynamic_cast(*src.get()); src.release(); return std::unique_ptr ret(dest_ptr); } 

Si vous n’utilisez que le pointeur downcast dans une petite étendue, une alternative consiste simplement à réduire la référence à l’object géré par l’ unique_ptr :

 auto derived = dynamic_cast(*pBase); derived.foo();