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