388 votes

Différence dans make_shared et normal shared_ptr en C++

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

Beaucoup de google et stackoverflow de postes de il y sur ce, mais je ne suis pas en mesure de comprendre pourquoi make_shared est plus efficace que d'utiliser directement shared_ptr.

Quelqu'un peut-il m'expliquer étape par étape de la séquence d'objets créés et les opérations effectuées par les deux de sorte que je vais être en mesure de comprendre comment make_shared est efficace. J'ai donné un exemple, ci-dessus pour la référence.

457voto

mpark Points 1607

La différence est que, std::make_shared effectue un tas de répartition, alors que l'appel de la std::shared_ptr constructeur effectue deux.

Où faire le tas allocations de se produire?

std::shared_ptr gère deux entités,

  • le bloc de contrôle (magasins de méta-données telles que la ref-nombre, type effacé deleter, etc)
  • l'objet géré

std::make_shared effectue un seul tas d'allocation de la comptabilité de l'espace nécessaire pour le bloc de contrôle et de données. Dans les autres cas, new Obj("foo") invoque un tas d'allocation pour la gestion de données et l' std::shared_ptr constructeur effectue une autre pour le bloc de contrôle.

Pour de plus amples informations, consultez les notes de mise en œuvre à cppreference.

I Mise À Jour: Exception-Sécurité

Depuis l'OP semble être vous demandez-vous à propos de l'exception de sécurité côté des choses, j'ai mis à jour ma réponse.

Considérons cet exemple,

void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")),
  std::shared_ptr<Rhs>(new Rhs("bar")));

Parce que le C++ permet d'ordre arbitraire de l'évaluation interne, les expressions, il est possible de la commande est:

  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. std::shared_ptr<Rhs>

Maintenant, supposons que nous avons une exception levée lors de l'étape 2 (eg. de mémoire d'exception, Rhs constructeur jeté quelques exception). Nous avons perdu la mémoire allouée à l'étape 1, puisque rien n'aura eu la chance de le nettoyer. Le cœur du problème, c'est que le pointeur brut avais pas passé à l' std::shared_ptr constructeur immédiatement.

Une façon de résoudre ce problème est de les faire sur des lignes séparées, de sorte que cet arbitraire de la commande ne peut pas se produire.

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

La meilleure façon de résoudre ce cours est d'utiliser std::make_shared à la place.

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

Mise à jour II: l'Inconvénient de l' std::make_shared

Citant Caseycommentaires:

Depuis là, il n'y a qu'une seule allocation, le pointee du mémoire ne peut pas être libéré jusqu'à ce que le bloc de contrôle est plus en cours d'utilisation. Un weak_ptr pouvez garder le contrôle de bloc en vie indéfiniment.

Pourquoi les instances de l' weak_ptrs à garder le contrôle de bloc en vie?

Il doit y avoir un moyen pour weak_ptrs pour déterminer si l'objet géré est toujours valide (par exemple. pour lock). Ils font cela en vérifiant le nombre de shared_ptrs propre à l'objet géré, qui est stocké dans le bloc de contrôle. Le résultat est que les blocs de contrôle sont en vie jusqu'à ce que l' shared_ptr compter et de l' weak_ptr comptent tous les deux frappé 0.

Retour à l' std::make_shared

Depuis std::make_shared fait un seul tas-allocation pour le bloc de contrôle et de l'objet géré, il n'existe aucun moyen pour libérer de la mémoire pour le bloc de contrôle et de l'objet géré de manière indépendante. Nous devons attendre jusqu'à ce que nous pouvons libérer à la fois le bloc de contrôle et de la gestion de l'objet, qui se trouve être jusqu'à ce qu'il n'y a aucun shared_ptrs ou weak_ptrs vivant.

Supposons que nous avons effectuée au lieu de deux tas-allocations pour le bloc de contrôle et de l'objet géré par new et shared_ptr constructeur. Ensuite, nous libérer de la mémoire pour les objets gérés (peut-être plus tôt) lorsqu'il n'y a shared_ptrs en vie, et de libérer de la mémoire pour le bloc de contrôle (peut-être plus tard) quand il n'y a aucun weak_ptrs vivant.

29voto

Mike Seymour Points 130519

Le pointeur partagé gère à la fois l'objet lui-même, et un petit objet contenant le compteur de référence et d'autres de ménage de données. make_shared peut allouer un bloc de mémoire pour contenir à la fois de ces; la construction d'un pointeur partagé à partir d'un pointeur à un objet alloué devez allouer un deuxième bloc pour stocker le nombre de références.

Ainsi que cette efficacité, à l'aide de make_shared signifie que vous n'avez pas besoin de traiter avec new premières et des pointeurs à tous, en donnant de meilleures exception de sécurité - il n'y a pas de possibilité de lancer une exception après l'affectation de l'objet, mais avant de l'affecter à l'pointeur intelligent.

6voto

Simon Ferquel Points 201

Si vous avez besoin d'une mémoire de l'alignement sur l'objet contrôlé par shared_ptr, vous ne pouvez pas compter sur make_shared, mais je pense que c'est la seule bonne raison de ne pas l'utiliser.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X