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:
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
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
.
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 int
s, 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é.
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 .