web-dev-qa-db-fra.com

std :: shared_ptr: reset () vs assignation

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);
  1. Les deux lignes suivantes sont-elles équivalentes:

    p = std::make_shared<int>(5);
    p.reset(new int(5));
    
  2. 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?

29
AlwaysLearning

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é.

22
NathanOliver

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.

6
Praetorian

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.

2
Alejandro

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.

0
Guvante