web-dev-qa-db-fra.com

création d'un shared_ptr à partir d'unique_ptr

Dans un morceau de code que j'ai passé en revue récemment, qui s'est bien compilé avec g++-4.6, J'ai rencontré une étrange tentative de créer un std::shared_ptr À partir de std::unique_ptr:

std::unique_ptr<Foo> foo...
std::make_shared<Foo>(std::move(foo));

Cela me semble plutôt étrange. Cela devrait être std::shared_ptr<Foo>(std::move(foo)); afaik, bien que je ne sois pas parfaitement familier avec les mouvements (et je sais que std::move N'est qu'un casting, rien n'est déplacé).

Vérification avec différents compilateurs sur ce SSC (NUC *) E

#include <memory>

int main()
{
   std::unique_ptr<int> foo(new int);
   std::make_shared<int>(std::move(foo));
}

Résultats de la compilation:

  • g ++ - 4.4.7 donne une erreur de compilation
  • g ++ - 4.6.4 compile sans aucune erreur
  • g ++ - 4.7.3 donne erreur interne du compilateur
  • g ++ - 4.8.1 donne une erreur de compilation
  • clang ++ - 3.2.1 compile sans aucune erreur

La question est donc: quel compilateur a raison en termes de norme? La norme exige-t-elle que ce soit une déclaration invalide, une déclaration valide ou est-ce simplement indéfini?

Ajout

Nous avons convenu que certains de ces compilateurs, tels que clang ++ et g ++ - 4.6.4, autorisent la conversion alors qu'ils ne devraient pas. Cependant, avec g ++ - 4.7.3 (qui produit une erreur de compilation interne sur std::make_shared<Foo>(std::move(foo));), rejette correctement int bar(std::move(foo));

En raison de cette énorme différence de comportement, je laisse la question telle qu'elle est, même si une partie de celle-ci serait responsable avec la réduction à int bar(std::move(foo));.


*) NUC: non universellement compilable

49
stefan

MISE À JOUR 2: Ce bug a été corrigé dans Clang dans r191150. GCC rejette le code avec un message d'erreur approprié.


MISE À JOUR: J'ai soumis un rapport de bogue . Le code suivant sur ma machine avec clang ++ 3.4 (trunk 191037)

#include <iostream>
#include <memory>

int main()
{
   std::unique_ptr<int> u_ptr(new int(42));

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;

   auto s_ptr = std::make_shared<int>(std::move(u_ptr));

   std::cout << "After move" << std::endl;

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;
   std::cout << " s_ptr.get() = " <<  s_ptr.get() << std::endl;
   std::cout << "*s_ptr       = " << *s_ptr       << std::endl;
}

imprime ceci:

 u_ptr.get() = 0x16fa010
*u_ptr       = 42
After move
 u_ptr.get() = 0x16fa010
*u_ptr       = 42
 s_ptr.get() = 0x16fa048
*s_ptr       = 1

Comme vous pouvez le voir, le unique_ptr n'a pas été déplacé. Le standard garantit qu'il devrait être nul après avoir été déplacé. Le shared_ptr pointe vers une valeur erronée.

Ce qui est étrange, c'est qu'il compile sans avertissement et que valgrind ne signale aucun problème, aucune fuite, aucune corruption de tas. Bizarre.

Le bon comportement est affiché si je crée s_ptr avec le shared_ptr ctor prenant une rvalue ref à un unique_ptr au lieu de make_shared:

#include <iostream>
#include <memory>

int main()
{
   std::unique_ptr<int> u_ptr(new int(42));

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;

   std::shared_ptr<int> s_ptr{std::move(u_ptr)};

   std::cout << "After move" << std::endl;

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   //std::cout << "*u_ptr       = " << *u_ptr       << std::endl; // <-- would give a segfault
   std::cout << " s_ptr.get() = " <<  s_ptr.get() << std::endl;
   std::cout << "*s_ptr       = " << *s_ptr       << std::endl;
}

Il imprime:

 u_ptr.get() = 0x5a06040
*u_ptr       = 42
After move
 u_ptr.get() = 0
 s_ptr.get() = 0x5a06040
*s_ptr       = 42

Comme tu vois, u_ptr est nul après le déplacement comme requis par la norme et s_ptr pointe vers la valeur correcte. C'est le comportement correct.


(La réponse d'origine.)

Comme Simple l'a souligné : "Sauf si Foo a un constructeur qui prend un std :: unique_ptr, il ne devrait pas compiler."

Pour développer un peu: make_shared transmet ses arguments au constructeur de T. Si T n'a pas de ctor qui pourrait accepter que unique_ptr<T>&& c'est une erreur de compilation.

Cependant, il est facile de corriger ce code et d'obtenir ce que vous voulez ( démo en ligne ):

#include <memory>
using namespace std;

class widget { };

int main() {

    unique_ptr<widget> uptr{new widget};

    shared_ptr<widget> sptr(std::move(uptr));
}

Le point est: make_shared n'est pas la bonne chose à utiliser dans cette situation. shared_ptr a un ctor qui accepte un unique_ptr<Y,Deleter>&&, voir (13) aux cteurs de shared_ptr .

32
Ali

Cela ne devrait pas être compilé. Si nous ignorons le caractère unique et le partage des pointeurs pendant un moment, c'est essentiellement en essayant de faire ceci:

int *u = new int;
int *s = new int(std::move(u));

Cela signifie que c'est dynamiquement créant un int et l'initialisant avec une référence rvalue à std::unique_ptr<int>. Pour ints, cela ne devrait tout simplement pas être compilé.

Pour une classe générale Foo, cela dépend de la classe. S'il a un constructeur prenant un std::unique_ptr<Foo> par valeur, const ref ou rvalue ref, cela fonctionnera (mais peut-être pas faire ce que l'auteur voulait). Dans d'autres cas, il ne devrait pas être compilé.

12
Angew

Voici un exemple réduit qui résonne de manière incorrecte:

struct ptr
{
  int* p;

  explicit operator bool() const { return p != nullptr; }
};

int main()
{
  ptr u{};
  int* p = new int(u);
}

Clang utilise l'opérateur de conversion booléen explicite pour initialiser le int (et le compilateur Intel aussi).

Clang 3.4 ne permet pas:

int i = int(u);

mais cela permet:

int* p = new int(u);

Je pense que les deux devraient être rejetés. (Clang 3.3 et ICC autorisent les deux.)

J'ai ajouté cet exemple au rapport de bogue .

5
Jonathan Wakely