Passer shared_ptr <Derived> comme shared_ptr <Base>


93

Quelle est la meilleure méthode pour passer un shared_ptrd'un type dérivé à une fonction qui prend un shared_ptrd'un type de base?

Je passe généralement shared_ptrs par référence pour éviter une copie inutile:

int foo(const shared_ptr<bar>& ptr);

mais cela ne fonctionne pas si j'essaye de faire quelque chose comme

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

je pourrais utiliser

foo(dynamic_pointer_cast<Base, Derived>(bar));

mais cela semble sous-optimal pour deux raisons:

  • A dynamic_castsemble un peu excessif pour une simple distribution dérivée à la base.
  • Si je comprends bien, dynamic_pointer_castcrée une copie (quoique temporaire) du pointeur à passer à la fonction.

Y a-t-il une meilleure solution?

Mise à jour pour la postérité:

Il s'est avéré être un problème de fichier d'en-tête manquant. De plus, ce que j'essayais de faire ici est considéré comme un anti-modèle. Généralement,

  • Les fonctions qui n'ont pas d'impact sur la durée de vie d'un objet (c'est-à-dire que l'objet reste valide pendant toute la durée de la fonction) doivent prendre une simple référence ou un pointeur, par exemple int foo(bar& b).

  • Les fonctions qui consomment un objet (c'est-à-dire sont les utilisateurs finaux d'un objet donné) devraient prendre une unique_ptrvaleur par, par exemple int foo(unique_ptr<bar> b). Les appelants doivent std::movela valeur dans la fonction.

  • Les fonctions qui prolongent la durée de vie d'un objet doivent prendre une shared_ptrvaleur par, par exemple int foo(shared_ptr<bar> b). Les conseils habituels pour éviter les références circulaires s'appliquent.

Voir la présentation de Herb Sutter Back to Basics pour plus de détails.


8
Pourquoi voulez-vous passer un shared_ptr? Pourquoi pas de référence const de barre?
ipc

2
Tout dynamiccasting n'est nécessaire que pour le downcasting. En outre, passer le pointeur dérivé devrait fonctionner correctement. Il va créer un nouveau shared_ptravec le même refcount (et l'augmenter) et un pointeur vers la base, qui se lie ensuite à la référence const. Puisque vous prenez déjà une référence, cependant, je ne vois pas pourquoi vous voulez en prendre un shared_ptr. Prenez un Base const&et appelez foo(*bar).
Xeo

@Xeo: Passer le pointeur dérivé (ie foo(bar)) ne fonctionne pas, du moins dans MSVC 2010.
Matt Kline

1
Qu'entendez-vous par «manifestement ne fonctionne pas»? Le code se compile et se comporte correctement; demandez-vous comment éviter de créer un temporaire shared_ptrà passer à la fonction? Je suis assez sûr qu'il n'y a aucun moyen d'éviter cela.
Mike Seymour

1
@Seth: Je ne suis pas d'accord. Je pense qu'il y a des raisons de passer un pointeur partagé par valeur, et il y a très peu de raisons de passer un pointeur partagé par référence (et tout cela sans préconiser des copies inutiles). Raisonnement ici stackoverflow.com/questions/10826541/…
R. Martinho Fernandes

Réponses:


47

Bien que Baseet Derivedsoient covariants et bruts, les pointeurs agissent en conséquence shared_ptr<Base>et neshared_ptr<Derived> sont pas covariants. Il dynamic_pointer_casts'agit de la manière correcte et la plus simple de gérer ce problème.

( Modifier: static_pointer_cast serait plus approprié car vous effectuez un cast de dérivé vers la base, ce qui est sûr et ne nécessite pas de vérifications à l'exécution. Voir les commentaires ci-dessous.)

Cependant, si votre foo()fonction ne souhaite pas participer à l'extension de la durée de vie (ou, plutôt, participer à la propriété partagée de l'objet), il est préférable d'accepter a const Base&et de déréférencer le shared_ptrlors de sa transmission foo().

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

En passant, comme les shared_ptrtypes ne peuvent pas être covariants, les règles des conversions implicites entre les types de retour covariants ne s'appliquent pas lors du retour des types de shared_ptr<T>.


39
Ils ne sont pas covariants, mais shared_ptr<Derived>sont implicitement convertibles en shared_ptr<Base>, donc le code devrait fonctionner sans manigances de casting.
Mike Seymour

9
Um, shared_ptr<Ty>a un constructeur qui prend a shared_ptr<Other>et effectue la conversion appropriée si Ty*est implicitement convertible en Other*. Et si un casting est nécessaire, celui-ci static_pointer_castest approprié ici, non dynamic_pointer_cast.
Pete Becker

Vrai, mais pas avec son paramètre de référence, comme dans la question. Il aurait besoin d'en faire une copie, peu importe. Mais, s'il utilise refs to shared_ptrpour éviter le nombre de références, alors il n'y a vraiment aucune bonne raison d'utiliser a shared_ptren premier lieu. Il est préférable d'utiliser à la const Base&place.
Bret Kuhns

@PeteBecker Voir mon commentaire à Mike sur le constructeur de conversion. Honnêtement, je n'en savais rien static_pointer_cast, merci.
Bret Kuhns

1
@TanveerBadar Pas sûr. Peut-être que cela n'a pas été compilé en 2012? (en utilisant spécifiquement Visual Studio 2010 ou 2012). Mais vous avez tout à fait raison, le code d'OP devrait absolument être compilé si la définition complète d'une classe / publiquement dérivée / est visible par le compilateur.
Bret Kuhns

32

Cela se produira également si vous avez oublié de spécifier l' héritage public sur la classe dérivée, c'est à dire si comme moi vous écrivez ceci:

class Derived : Base
{
};

classest pour les paramètres de modèle; structsert à définir les classes. (C'est au plus 45% une blague.)
Davis Herring

Cela devrait certainement être considéré comme la solution, il n'y a pas besoin de casting car il ne manque que du public.
Alexis Paques

12

On dirait que vous essayez trop fort. shared_ptrest bon marché à copier; c'est l'un de ses objectifs. Les faire circuler par référence ne fait pas grand-chose. Si vous ne voulez pas de partage, passez le pointeur brut.

Cela dit, il y a deux façons de faire cela auxquelles je peux penser du haut de ma tête:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));

9
Non, ils ne sont pas bon marché à copier, ils doivent être passés par référence dans la mesure du possible.
Seth Carnegie

6
@SethCarnegie - Herb a-t-il profilé votre code pour voir si le passage par valeur était un goulot d'étranglement?
Pete Becker

25
@SethCarnegie - cela ne répond pas à la question que j'ai posée. Et, pour ce que ça vaut, j'ai écrit l' shared_ptrimplémentation fournie par Microsoft.
Pete Becker

6
@SethCarnegie - vous avez l'heuristique à l'envers. Les optimisations manuelles ne doivent généralement pas être effectuées à moins que vous ne puissiez montrer qu'elles sont nécessaires.
Pete Becker

21
Ce n'est qu'une optimisation "prématurée" si vous avez besoin d'y travailler. Je ne vois aucun problème à adopter des idiomes efficaces plutôt que des idiomes inefficaces, que cela fasse une différence dans un contexte particulier ou non.
Mark Ransom

11

Vérifiez également que le #includefichier d'en-tête contenant la déclaration complète de la classe dérivée se trouve dans votre fichier source.

J'ai eu ce problème. Le std::shared<derived>ne serait pas jeté std::shared<base>. J'avais déclaré en avant les deux classes afin que je puisse contenir des pointeurs vers elles, mais parce que je n'avais pas #includele compilateur ne pouvait pas voir qu'une classe était dérivée de l'autre.


1
Wow, je ne m'y attendais pas mais cela l'a corrigé pour moi. Je faisais très attention et n'incluais que les fichiers d'en-tête là où j'en avais besoin, donc certains d'entre eux n'étaient que dans les fichiers source et les déclaraient dans les en-têtes comme vous l'avez dit.
jigglypuff

Un compilateur stupide est stupide. C'était mon problème. Merci!
Tanveer Badar le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.