c++ {*ce} à l'intérieur des accolades
Le code suivant compile bien:
g++ -std=c++11 test.cpp -Wall -Wextra -Wfatal-errors && ./a.out
Cependant, si je supprime les accolades de {*this}
et utilise *this
à la place, je vais faire face à une erreur:
Erreur: utilisation de la fonction supprimée 'Obj:: Position:: Position(Obj:: Position&&)'
Quelle est la différence entre {*this}
et *this
?
class Obj
{
template<bool> friend class Position;
double data;
public:
class Position
{
const Obj& ref;
public:
inline Position(const Obj& ref): ref(ref){}
inline Position(Position const &) = delete;
inline Position(Position &&) = delete;
};
inline Obj(){}
inline Obj(const double &data): data(data){}
inline auto get_pos() const-> Position{return {*this};} /* <--- here */
inline auto get_pos()-> Position{return {*this};}
};
int main()
{
return 0;
}
4 réponses
La différence entre les deux est vraiment assez subtile. C++11 introduit la fonction initialisation de la liste (aussi parfois appelé accolade de l'initialisation):
Avant C++11, lorsque vous voulez par défaut-la construction et l'objet o
de type Obj
et construire un Position p
à partir de o
, vous avez dû vous écrire
Obj o; // default construct o
Obj::Position p(o); // construct p using Position(Obj const&)
Une erreur courante pour les débutants (surtout avec un fond Java) était d'essayer d'écrire ceci:
Obj o(); // mistake: declares a function o returning an Obj
Obj::Position p(o); // error: no constructor takes a function
La première ligne déclare un function , et le second essaie de créer un Position
en utilisant un constructeur qui prend un pointeur de fonction comme argument. Afin d'avoir une syntaxe d'initialisation uniforme, C++11 introduit l'initialisation de liste:
Obj oo{}; // new in C++11: default construct o of type Obj
Obj::Position p1(oo); // possible before (and after) C++11
Obj::Position p2{oo}; // new in C++11: construct p2 using Position(Obj const&)
Cette nouvelle syntaxe fonctionne également dans return
- instructions, et cela conduit à la réponse à votre question: la différence entre return {*this};
et return *this;
est que le premier initialise la valeur de retour directement de *this
, alors que le dernier convertit d'abord *this
en un objet temporaire Position
, puis initialise indirectement la valeur de retour à partir de ce temporaire , qui échoue car les constructeurs copy et move ont été explicitement supprimés.
Comme l'ont noté les affiches précédentes, la plupart des compilateurs élide ces objets temporaires car ils ne sont vraiment utiles à rien; mais cela n'est possible que s'ils peuvent être utilisés en théorie car un constructeur copy ou move est disponible. Parce que cela conduit à beaucoup de confusion (pourquoi ai-je besoin d'accolades autour de ma déclaration de retour? est le compilateur va éluder la copie ou pas?), C++17 supprime ces temporaires inutiles, et initialise la valeur de retour directement dans les deux cas (return {*this};
et return *this
).
Vous pouvez essayer cela en utilisant un compilateur qui prend en charge C++17. Dans clang 4.0 ou gcc 7.1, vous pouvez passer --std=c++1z
, et votre code doit être compilé correctement avec et sans accolades.
Lorsque les accolades sont présentes, vous êtes copier-liste-initialisation la valeur de retour, aucun constructeur copy / move n'est impliqué. La valeur de retour est construite sur place en utilisant le constructeur Position(const Obj&)
.
Notez que le code échouerait à compiler même avec les accolades si vous avez créé le constructeur Position(const Obj&)
explicit
car copy-list-initialization ne permet pas d'appeler des constructeurs explicites.
Si vous omettez les accolades, alors sémantiquement, un objet temporaire Position
est construit dans la fonction, et la valeur de retour est move construit à partir de ce temporaire. En pratique, la plupart des implémentations éliront la construction move, mais cela nécessite toujours la présence d'un constructeur move viable, ce qui n'est pas le cas ici car il a été explicitement supprimé. C'est la raison pour laquelle votre code ne compilera pas sans accolades.
En utilisant un compilateur C++17, votre code compilera même sans les accolades à cause de garanti copier-élision.
C'est un bon! En effet, return {...}
signifie "retourner un objet du type de retour de la fonction initialisé avec list initializer ...".
Les initialiseurs de liste sont décrits plus en détail ici:
Http://en.cppreference.com/w/cpp/language/list%20initialization
Donc, la différence est que {*this}
appelle cela:
inline Position(const Obj& ref): ref(ref){}
Alors que *this
essaie de convertir Obj&
en Position
en utilisant des opérateurs d'affectation explicitement supprimés (avant C++11, ils devraient être fait private
, et vous obtiendriez un message d'erreur encore plus confus si les initialiseurs de liste seraient disponibles...):
inline Position(Position const &) = delete;
inline Position(Position &&) = delete;
Franchement, en utilisant votre classe et la main () suivante:
int main()
{
Obj o1;
cout<<"get position"<<'\n';
Obj::Position pos= o1.get_pos();
cout.flush();
return 0;
}
Il ne compile pas (gcc/mingw) dans les deux cas (- std = C++14), avec ou sans accolades et il se plaint de la position manquante(Position&&) constructeur, qui est supprimé. C'est raisonnable car il semble que dans les deux cas une construction de l'objet de retour temporaire est effectuée, qui doit ensuite être déplacée vers la destination. Ce qui est impossible car move constructor est supprimé. Inversement, en utilisant -std = c++17 le signaler compile dans les deux cas (avec ou sans accolades) car, très probablement, nous atteignons l'optimisation de la valeur de Retour Garantie de c++17. Espérons que cette aide.