Downcasting shared_ptr <Base> en shared_ptr <Derived>?

102

Mise à jour: le shared_ptr dans cet exemple est comme celui de Boost, mais il ne prend pas en charge shared_polymorphic_downcast (ou dynamic_pointer_cast ou static_pointer_cast d'ailleurs)!

J'essaye d'initialiser un pointeur partagé vers une classe dérivée sans perdre le nombre de références:

struct Base { };
struct Derived : public Base { };
shared_ptr<Base> base(new Base());
shared_ptr<Derived> derived;

// error: invalid conversion from 'Base* const' to 'Derived*'
derived = base;  

Jusqu'ici tout va bien. Je ne m'attendais pas à ce que C ++ convertisse implicitement Base * en Derived *. Cependant, je veux la fonctionnalité exprimée par le code (c'est-à-dire maintenir le nombre de références tout en abaissant le pointeur de base). Ma première pensée a été de fournir un opérateur de cast dans Base afin qu'une conversion implicite en Derived puisse avoir lieu (pour les pédants: je vérifierais que le down cast est valide, ne vous inquiétez pas):

struct Base {
  operator Derived* ();
}
// ...
Base::operator Derived* () {
  return down_cast<Derived*>(this);
}

Eh bien, cela n'a pas aidé. Il semble que le compilateur a complètement ignoré mon opérateur de transtypage. Des idées sur la façon dont je pourrais faire fonctionner le devoir shared_ptr? Pour les points supplémentaires: quel type de type Base* constest-ce? const Base*Je comprends, mais Base* const? A quoi fait constréférence dans ce cas?

Lajos Nagy
la source
Pourquoi avez-vous besoin d'un shared_ptr <Derived> au lieu d'un shared_ptr <Base>?
Projet de loi du
3
Parce que je souhaite accéder aux fonctionnalités de Derived qui ne sont pas dans Base, sans cloner l'objet (je veux un seul objet, référencé par deux pointeurs partagés). Au fait, pourquoi les opérateurs de casting ne fonctionnent-ils pas?
Lajos Nagy

Réponses:

109

Vous pouvez utiliser dynamic_pointer_cast. Il est soutenu par std::shared_ptr.

std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived =
               std::dynamic_pointer_cast<Derived> (base);

Documentation: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

De plus, je ne recommande pas d'utiliser l'opérateur de cast dans la classe de base. Un casting implicite comme celui-ci peut devenir la source de bogues et d'erreurs.

-Update: Si le type n'est pas polymorphe, std::static_pointer_castpeut être utilisé.

Massood Khaari
la source
4
Je n'ai pas compris dès la première ligne qu'il n'utilise pas std::shared_ptr. Mais d'après les commentaires de la première réponse, j'ai déduit qu'il n'utilisait pas de boost, donc il l'utilisait peut-être std::shared_ptr.
Massood Khaari
D'ACCORD. Désolé. Il devrait mieux préciser qu'il utilise une implémentation personnalisée.
Massood Khaari
47

Je suppose que vous utilisez boost::shared_ptr... Je pense que vous voulez dynamic_pointer_castou shared_polymorphic_downcast.

Cependant, ceux-ci nécessitent des types polymorphes.

quel genre de type Base* constest? const Base*Je comprends, mais Base* const? A quoi fait constréférence dans ce cas?

  • const Base *est un pointeur mutable vers une constante Base.
  • Base const *est un pointeur mutable vers une constante Base.
  • Base * constest un pointeur constant vers un mutable Base.
  • Base const * constest un pointeur constant vers une constante Base.

Voici un exemple minimal:

struct Base { virtual ~Base() { } };   // dynamic casts require polymorphic types
struct Derived : public Base { };

boost::shared_ptr<Base> base(new Base());
boost::shared_ptr<Derived> derived;
derived = boost::static_pointer_cast<Derived>(base);
derived = boost::dynamic_pointer_cast<Derived>(base);
derived = boost::shared_polymorphic_downcast<Derived>(base);

Je ne sais pas si c'était intentionnel que votre exemple crée une instance du type de base et la jette, mais cela sert à bien illustrer la différence.

La static_pointer_castvolonté "faites-le simplement". Cela entraînera un comportement indéfini (un Derived*pointage sur la mémoire allouée et initialisée par Base) et causera probablement un plantage, ou pire. Le comptage de référence basesera incrémenté.

Le dynamic_pointer_castaboutira à un pointeur nul. Le décompte des références baserestera inchangé.

Le shared_polymorphic_downcastaura le même résultat qu'un cast statique, mais déclenchera une assertion, plutôt que de sembler réussir et de conduire à un comportement indéfini. Le comptage de référence basesera incrémenté.

Voir (lien mort) :

Parfois, il est un peu difficile de décider d'utiliser static_castou dynamic_cast, et vous souhaiteriez pouvoir avoir un peu des deux mondes. Il est bien connu que dynamic_cast a une surcharge d'exécution, mais c'est plus sûr, alors que static_cast n'a pas de surcharge du tout, mais il peut échouer silencieusement. Comme ce serait bien si vous pouviez l'utiliser shared_dynamic_castdans les versions de débogage et shared_static_castdans les versions de version. Eh bien, une telle chose est déjà disponible et s'appelle shared_polymorphic_downcast.

Tim Sylvester
la source
Malheureusement, votre solution dépend de la fonctionnalité Boost qui a été délibérément exclue de l'implémentation shared_ptr que nous utilisons (ne demandez pas pourquoi). Quant à l'explication const, elle a beaucoup plus de sens maintenant.
Lajos Nagy
3
À moins d'implémenter les autres shared_ptrconstructeurs (prenant static_cast_taget dynamic_cast_tag), vous ne pouvez pas faire grand-chose. Tout ce que vous faites à l'extérieur shared_ptrne pourra pas gérer le refcount. - Dans une conception OO "parfaite", vous pouvez toujours utiliser le type de base, sans jamais avoir besoin de savoir ni de se soucier de ce qu'est le type dérivé, car toutes ses fonctionnalités sont exposées via des interfaces de classe de base. Peut-être avez-vous juste besoin de repenser la raison pour laquelle vous devez d'abord décliner.
Tim Sylvester
1
@Tim Sylvester: mais, C ++ n'est pas un langage OO "parfait"! :-) les down-cast ont leur place dans un langage OO non parfait
Steve Folly
4

Si quelqu'un arrive avec boost :: shared_ptr ...

C'est ainsi que vous pouvez effectuer un downcast vers le Boost dérivé shared_ptr. En supposant que Derived hérite de Base.

boost::shared_ptr<Base> bS;
bS.reset(new Derived());

boost::shared_ptr<Derived> dS = boost::dynamic_pointer_cast<Derived,Base>(bS);
std::cout << "DerivedSPtr  is: " << std::boolalpha << (dS.get() != 0) << std::endl;

Assurez-vous que la classe / structure 'Base' a au moins une fonction virtuelle. Un destructeur virtuel fonctionne également.

Mitendra
la source