C'est une question fondamentale, mais je n'ai pas trouvé de message précédent à ce sujet. Le titre de la question suivante semble être la même que la mienne, mais la question elle-même ne correspond pas au titre: vaut-il mieux utiliser shared_ptr.reset ou operator =?
Je suis confus quant au but de la fonction membre reset()
de std::shared_ptr
: en quoi contribue-t-elle à l'opérateur d'affectation?
Pour être concret, étant donné la définition:
auto p = std::make_shared<int>(1);
Les deux lignes suivantes sont-elles équivalentes:
p = std::make_shared<int>(5);
p.reset(new int(5));
Qu'en est-il de ceux-ci:
p = nullptr;
p.reset();
Si les deux lignes sont équivalentes dans les deux cas, alors quel est le but de reset()
?
EDIT: Permettez-moi de reformuler la question pour mieux souligner son point. La question qui se pose est la suivante: existe-t-il un cas où reset()
nous permet de réaliser quelque chose qui ne serait pas aussi facilement réalisable sans cela?
Lors de l'utilisation de reset()
, le paramètre transmis pour réinitialiser ne doit pas nécessairement être un objet géré (ni ne peut l'être); alors qu'avec =
le côté droit doit être un objet géré.
Donc, ces deux lignes vous donnent le même résultat final:
p = std::make_shared<int>(5); // assign to a newly created shared pointer
p.reset(new int(5)); // take control of a newly created pointer
Mais nous ne pouvons pas faire:
p = new int(5); // compiler error no suitable overload
p.reset(std::make_shared<int>(5).get()); // uh oh undefined behavior
Sans reset()
, vous ne seriez pas en mesure de réaffecter un pointeur partagé à un autre pointeur brut sans créer un pointeur partagé et l'affecter. Sans =
, vous ne pourriez pas faire pointer un pointeur partagé vers un autre pointeur partagé.
Il est possible que reset
évite une allocation de mémoire dynamique dans certains cas. Considérons le code
std::shared_ptr<int> p{new int{}}; // 1
p.reset(new int{}); // 2
Sur la ligne 1, 2 affectations de mémoire dynamiques sont en cours, une pour l'objet int
et une seconde pour le bloc de contrôle du shared_ptr
qui suivra le nombre de références fortes/faibles à l'objet géré.
Sur la ligne 2, il existe à nouveau une allocation de mémoire dynamique pour un nouvel objet int
. Dans le corps de reset
, le shared_ptr
déterminera qu'il n'y a pas d'autres références fortes à la int
précédemment gérée, elle doit donc delete
. Etant donné qu'il n'y a pas non plus de références faibles, le bloc de contrôle pourrait également être libéré, mais dans ce cas, il serait prudent que l'implémentation réutilise le même bloc de contrôle, car il faudrait sinon en affecter un nouveau.
Le comportement ci-dessus ne serait pas possible si vous deviez toujours utiliser une affectation.
std::shared_ptr<int> p{new int{}}; // 1
p = std::shared_ptr<int>{new int{}}; // 2
Dans ce cas, le deuxième appel du constructeur shared_ptr
sur la ligne 2 a déjà alloué un bloc de contrôle. p
devra donc libérer son propre bloc de contrôle existant.
Je ne vais pas expliquer la raison de votre première sous-question concernant la différence entre la construction via make_shared
ou un pointeur, car cette différence est mise en évidence à plusieurs endroits différents, y compris cette excellente question .
Cependant, je pense qu’il est constructif de faire la distinction entre l’utilisation de reset
et de operator=
. Le premier abandonne la propriété de la ressource gérée par le shared_ptr
, soit en la détruisant si le shared_ptr
était le seul propriétaire, soit en décrémentant le compte de références. Ce dernier implique la propriété partagée avec un autre shared_ptr
(sauf si vous construisez un déménagement).
Comme je l'ai mentionné dans les commentaires, il est important que le pointeur transmis à reset
ne soit pas la propriété d'un autre pointeur partagé ou unique, car il produirait un comportement indéfini lors de la destruction des deux gestionnaires indépendants. delete
la ressource.
Un cas d'utilisation de reset
pourrait être l'initialisation paresseuse d'une ressource partagée. Vous voulez seulement que le shared_ptr
gère certaines ressources, la mémoire par exemple, si vous en avez vraiment besoin. Faire une allocation pure et simple, telle que:
std::shared_ptr<resource> shared_resource(new resource(/*construct a resource*/));
pourrait être inutile si ce n’est jamais réellement nécessaire. Pour ce faire avec une initialisation différée, quelque chose comme ceci peut s’appliquer:
std::shared_ptr<resource> shared_resource;
void use_resource()
{
if(!shared_resource)
{
shared_resource.reset(new resource(...));
}
shared_resource->do_foo();
}
Utiliser reset
dans ce cas est plus concis que de faire une swap
ou d’attribuer à un shared_ptr
temporaire.
reset()
modifie l'objet géré d'un shared_ptr
existant.
p = std :: shared_ptr (new int (5)); et p.reset (new int (5));
La première consiste à créer un nouveau shared_ptr
et à le déplacer dans une variable. Ce dernier ne crée pas de nouvel objet, il change simplement le pointeur sous-jacent géré par le shared_ptr
.
En d'autres termes, les deux sont destinés à être utilisés dans des cas différents. L'affectation est pour quand vous avez un shared_ptr
et reset
pour quand vous avez un pointeur brut.
Une autre chose à garder à l'esprit est que shared_ptr
était disponible dans Boost avant que l'attribution de mouvement n'existe et influence fortement la dernière version. Sans affectation de déplacement, il est avantageux de modifier un shared_ptr
sans en créer une copie, car cela vous évite de conserver la comptabilité de l'objet supplémentaire.