créer un shared_ptr à partir de unique_ptr

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

 std::unique_ptr foo... std::make_shared(std::move(foo)); 

Cela me semble plutôt étrange. Cela devrait être std::shared_ptr(std::move(foo)); Même si je ne suis pas parfaitement au courant des mouvements (et je sais que std::move est seulement un casting, rien ne bouge).

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

 #include  int main() { std::unique_ptr foo(new int); std::make_shared(std::move(foo)); } 

Résultats de compilation:

  • g ++ – 4.4.7 donne une erreur de compilation
  • g ++ – 4.6.4 comstack sans erreur
  • g ++ – 4.7.3 donne une erreur interne au compilateur
  • g ++ – 4.8.1 donne une erreur de compilation
  • clang ++ – 3.2.1 comstack sans erreur

Donc, la question est: quel compilateur a raison en termes de norme? La norme exige-t-elle que cette déclaration soit invalide, valide ou simplement indéfinie?

Une addition

Nous avons convenu que certains de ces compilateurs, tels que clang ++ et g ++ – 4.6.4, permettent la conversion alors qu’ils ne le devraient pas. Cependant, avec g ++ – 4.7.3 (qui produit une erreur interne du compilateur sur std::make_shared(std::move(foo)); ), rejette correctement int bar(std::move(foo));

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


*) NUC: pas universellement compilable

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


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

 #include  #include  int main() { std::unique_ptr 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(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, l' unique_ptr n'a pas été déplacé depuis. Le standard garantit qu'il devrait être nul après avoir été déplacé. Le shared_ptr pointe vers une valeur incorrecte.

La chose étrange est qu’elle comstack sans avertissement et que valgrind ne signale aucun problème, aucune fuite, aucune corruption de tas. Bizarre.

Le comportement correct est montré si je crée s_ptr avec le ctor shared_ptr prenant une valeur de référence à un unique_ptr au lieu de make_shared :

 #include  #include  int main() { std::unique_ptr 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 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 vous le voyez, u_ptr est nul après le déplacement, comme requirejs par le standard, et s_ptr pointe vers la valeur correcte. C'est le comportement correct.


(La réponse originale.)

Comme Simple l'a fait remarquer : "Sauf si Foo a un constructeur qui prend un std :: unique_ptr, il ne devrait pas être compilé."

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

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

 #include  using namespace std; class widget { }; int main() { unique_ptr uptr{new widget}; shared_ptr sptr(std::move(uptr)); } 

Le point est le suivant: make_shared est la mauvaise chose à utiliser dans cette situation. shared_ptr a un ctor qui accepte un unique_ptr&& , voir (13) aux moteurs de shared_ptr .

Cela ne devrait pas être compilé. Si nous négligeons l’unicité et le partage des indicateurs pendant un moment, nous essayons essentiellement de le faire:

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

Cela signifie qu’il crée dynamicment un int et l’initialise avec une référence rvalue à std::unique_ptr . Pour int s, cela ne devrait simplement pas être compilé.

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

Voici un exemple réduit qui comstack incorrectement:

 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 explicite pour initialiser l’ 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 permettent les deux.)

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