Il s'agit de sémantique dans certains cas. Ce n'est pas très évident avec les constructeurs par défaut, mais cela devient évident avec d'autres fonctions membres générées par le compilateur.
Pour le constructeur par défaut, il aurait été possible de considérer tout constructeur par défaut avec un corps vide comme candidat à être un constructeur trivial, tout comme l'utilisation de =default
. Après tout, les anciens constructeurs par défaut vides étaient du C++ légal.
struct S {
int a;
S() {} // C++ légal
};
Que le compilateur comprenne ou non que ce constructeur est trivial est sans importance dans la plupart des cas en dehors des optimisations (manuelles ou celles du compilateur).
Cependant, cette tentative de traiter les corps de fonctions vides comme "par défaut" échoue complètement pour d'autres types de fonctions membres. Considérez le constructeur de copie :
struct S {
int a;
S() {}
S(const S&) {} // légal, mais sémantiquement incorrect
};
Dans le cas ci-dessus, le constructeur de copie écrit avec un corps vide est maintenant incorrect. Il ne copie plus rien en réalité. C'est un ensemble de sémantiques très différentes de celles du constructeur de copie par défaut. Le comportement souhaité exige que vous écriviez du code :
struct S {
int a;
S() {}
S(const S& src) : a(src.a) {} // corrigé
};
Même avec ce cas simple, cependant, il devient beaucoup plus contraignant pour le compilateur de vérifier que le constructeur de copie est identique à celui qu'il générerait lui-même ou de voir que le constructeur de copie est trivial (équivalent à un memcpy
, essentiellement). Le compilateur devrait vérifier chaque expression d'initialisation de membre et s'assurer qu'elle est identique à l'expression d'accès au membre correspondant de la source et rien d'autre, s'assurer qu'aucun membre n'est laissé avec une construction par défaut non triviale, etc. C'est à l'envers par rapport au processus que le compilateur utiliserait pour vérifier que ses propres versions générées de cette fonction sont triviales.
Considérez ensuite l'opérateur d'affectation par copie qui peut devenir encore plus compliqué, surtout dans le cas non trivial. C'est beaucoup de code répétitif que vous ne voulez pas avoir à écrire pour de nombreuses classes, mais que vous êtes quand même obligé d'écrire en C++03 :
struct T {
std::shared_ptr b;
T(); // les définitions habituelles
T(const T&);
T& operator=(const T& src) {
if (this != &src) // pas vraiment nécessaire pour cet exemple
b = src.b; // opération non triviale
return *this;
};
C'est un cas simple, mais c'est déjà plus de code que vous souhaiteriez jamais être obligé d'écrire pour un type aussi simple que T
(surtout une fois que nous ajoutons les opérations de déplacement). Nous ne pouvons pas nous fier au corps vide pour signifier "remplir les valeurs par défaut" car le corps vide est déjà parfaitement valide et a une signification claire. En fait, si le corps vide était utilisé pour signifier "remplir les valeurs par défaut", il n'y aurait aucun moyen de créer explicitement un constructeur de copie sans effet ou similaire.
C'est à nouveau une question de cohérence. Le corps vide signifie "ne rien faire" mais pour des choses comme les constructeurs de copie, vous ne voulez vraiment pas dire "ne rien faire" mais plutôt "faire toutes les choses que vous feriez normalement si elles n'étaient pas supprimées". D'où l'utilisation de =default
. C'est nécessaire pour surmonter les fonctions membres générées par le compilateur supprimées telles que les constructeurs et les opérateurs d'affectation par copie. Il est alors tout simplement "évident" de le faire fonctionner également pour le constructeur par défaut.
Il aurait peut-être été intéressant de considérer les constructeurs par défaut avec des corps vides et des constructeurs de membres/bases triviaux également comme triviaux, tout comme avec =default
afin de rendre le code plus optimal dans certains cas, mais la plupart du code de bas niveau se basant sur des constructeurs par défaut triviaux pour les optimisations dépend également de constructeurs de copie triviaux. Si vous devez aller "corriger" tous vos anciens constructeurs de copie, il n'est vraiment pas très difficile de devoir corriger également tous vos anciens constructeurs par défaut. C'est aussi beaucoup plus clair et évident d'utiliser =default
pour indiquer vos intentions.
Il y a quelques autres choses que les fonctions membres générées par le compilateur feront que vous devrez explicitement modifier pour supporter, également. Le support de constexpr
pour les constructeurs par défaut en est un exemple. C'est tout simplement plus simple mentalement d'utiliser =default
que de devoir marquer les fonctions avec tous les autres mots-clés spéciaux et autres implicites de =default
et c'était l'un des thèmes de C++11 : rendre le langage plus simple. Il a encore plein de défauts et de compromis de compatibilité arrière mais il est clair qu'il constitue un grand pas en avant par rapport à C++03 en termes de facilité d'utilisation.
41 votes
Pinailler :
default
n'est pas un nouveau mot-clé, c'est simplement une nouvelle utilisation d'un mot-clé déjà réservé.1 votes
Possible duplicate
1 votes
Peut-être Cette question pourra vous aider.
11 votes
En plus des autres réponses, je soutiendrais également que '= default;' est plus explicite.
2 votes
Connexe : stackoverflow.com/questions/13576055/…
0 votes
Aussi lié: Signification de
= delete
après la déclaration d'une fonction0 votes
@Mark vraiment ?? quand vous faites ctor() {} il est très clair que cela ne fait rien et utilise le constructeur par défaut pour les classes de base. Quand vous faites = default; vous devez lire des pages de livres cpp pour savoir ce que le compilateur fait réellement.