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
?
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.
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);
}
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.
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.
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_ptr
s 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).
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);
}
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();
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