web-dev-qa-db-fra.com

Dois-je transmettre un shared_ptr par référence?

Quelles sont les meilleures pratiques pour passer un shared_ptr?

Actuellement, je passe les arguments de la fonction shared_ptr comme ceci:

void function1( shared_ptr<TYPE>& value );
89
Ben Crowhurst

Dans des circonstances contrôlées, vous pouvez transmettre le pointeur partagé par référence constante . Assurez-vous que personne ne supprime simultanément l'objet, même si cela ne devrait pas être trop difficile si vous faites attention à qui vous donnez des références.

En général, vous devez passer le pointeur partagé comme une copie droite . Cela lui donne la sémantique voulue: chaque étendue contenant une copie du pointeur partagé garde l'objet en vie grâce à son "partage" dans la propriété.

La seule raison pour laquelle on ne passe pas toujours par valeur est que la copie d'un pointeur partagé a un prix donné en raison de la mise à jour du compte de références atomique; Cependant, cela pourrait ne pas être une préoccupation majeure.


Digression facultative:

Puisque la question principale a reçu une réponse, il est peut-être instructif de considérer quelques façons dont vous devriez jamais utiliser un pointeur partagé. Voici une petite expérience de pensée. Définissons un type de pointeur partagé SF = std::shared_ptr<Foo>. Afin de considérer les références plutôt que de passer des arguments de fonction, examinons le type RSF = std::reference_wrapper<T>. Autrement dit, si nous avons un pointeur partagé SF p(std::make_shared<Foo>());, nous pouvons alors créer un wrapper de référence avec une sémantique de valeur via RSF w = std::ref(p);. Voilà pour la configuration.

Maintenant, tout le monde sait que les conteneurs de pointeurs sont des champs de mines. Donc std::vector<Foo*> Sera un cauchemar à entretenir et de nombreux bogues résulteront d'une gestion incorrecte de la durée de vie. Ce qui est pire sur le plan conceptuel, c’est qu’on ne sait jamais exactement qui possède les objets dont les pointeurs sont stockés par le conteneur. Les pointeurs pourraient même être un mélange de pointeurs sur des objets dynamiques, des objets automatiques et des déchets. Personne ne peut dire. La solution standard consiste donc à utiliser std::vector<SF> À la place. C'est la bonne façon d'utiliser le pointeur partagé. D'autre part, ce que vous ne devez jamais utiliser est std::vector<RSF> - c'est un monstre ingérable qui est en réalité très similaire au vecteur d'origine des pointeurs nus! Par exemple, il n'est pas clair si l'objet auquel vous tenez une référence est toujours en vie. Prendre une référence du pointeur partagé a totalement nui à son objectif.

Pour un deuxième exemple, supposons que nous ayons un pointeur partagé SF p Comme auparavant. Nous avons maintenant une fonction int foo(SF) que nous voulons exécuter simultanément. Le std::thread(foo, p) habituel fonctionne très bien, car le constructeur du thread fait une copie de ses arguments. Cependant, si nous avions dit std::thread(foo, std::ref(p)), nous aurions eu toutes sortes de problèmes: le pointeur partagé dans l'étendue de l'appel pourrait expirer et détruire l'objet, et il resterait une référence suspendue et un pointeur non valide. !

J'espère que ces deux exemples certes assez artificiels éclaircissent un peu la lumière lorsque vous voulez vraiment que vos pointeurs partagés soient échangés par copy . Dans un programme bien conçu, il devrait toujours être clair qui est responsable de quelles ressources, et lorsqu'il est utilisé correctement, le pointeur partagé est un excellent outil pour le travail.

95
Kerrek SB

Cela dépend de ce que tu veux. L'appelé doit-il partager la propriété de l'objet? Ensuite, il a besoin de sa propre copie du shared_ptr. Alors passez le par valeur.

Si une fonction a simplement besoin d'accéder à un objet appartenant à l'appelant, continuez et passez par référence (const), pour éviter la surcharge de copier le shared_ptr.

La meilleure pratique en C++ est toujours d'avoir une sémantique de propriété clairement définie pour vos objets. Il n'y a pas de "toujours faire ceci" universel pour remplacer la pensée actuelle .

Si vous toujours passez des pointeurs partagés par valeur, cela devient coûteux (car ils sont beaucoup plus coûteux à copier qu'un pointeur brut). Si vous jamais faites-le, il est inutile d'utiliser un pointeur partagé en premier lieu.

Copiez le pointeur partagé lorsqu'une nouvelle fonction ou un nouvel objet doit partager la propriété de la pointee.

25
jalf

Passez-le par référence const si vous passez par référence. Cela montre clairement que vous passez par arbitre pour des raisons de performances. De plus, utilisez make_shared quand vous le pouvez, car cela économisera une indirection, ce qui donnera un coup de pouce à la performance.

8
Kate Gregory