Disons que j'ai un type et que je veux rendre son constructeur par défaut privé. J'écris ce qui suit:
class C {
C() = default;
};
int main() {
C c; // error: C::C() is private within this context (g++)
// error: calling a private constructor of class 'C' (clang++)
// error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC)
auto c2 = C(); // error: as above
}
Génial.
Mais alors, le constructeur s'avère ne pas être aussi privé que je le pensais:
class C {
C() = default;
};
int main() {
C c{}; // OK on all compilers
auto c2 = C{}; // OK on all compilers
}
Cela me semble être un comportement très surprenant, inattendu et explicitement indésirable. Pourquoi est-ce correct?
C c{};
initialisation de l'agrégat n'est-elle pas donc aucun constructeur n'est appelé?C
comme un agrégat.=default
ctor public , cela semblerait plus raisonnable. Mais le=default
ctor privé semble être une chose importante qui ne doit pas être ignorée. De plus,class C { C(); } inline C::C()=default;
être assez différent est quelque peu surprenant.Réponses:
L'astuce est en C ++ 14 8.4.2 / 5 [dcl.fct.def.default]:
Ce qui signifie que
C
le constructeur par défaut de ce constructeur n'est en fait pas fourni par l'utilisateur, car il a été explicitement défini par défaut sur sa première déclaration. En tant que tel,C
n'a pas de constructeurs fournis par l'utilisateur et est donc un agrégat selon 8.5.1 / 1 [dcl.init.aggr]:la source
C{}
fonctionne même si le constructeur estdelete
d.Vous n'appelez pas le constructeur par défaut, vous utilisez l'initialisation d'agrégat sur un type d'agrégat. Les types d'agrégats sont autorisés à avoir un constructeur par défaut, à condition qu'il soit défini par défaut là où il a été déclaré pour la première fois:
Depuis [dcl.init.aggr] / 1 :
et de [dcl.fct.def.default] / 5
Ainsi, nos exigences pour un agrégat sont:
C
répond à toutes ces exigences.Naturellement, vous pouvez vous débarrasser de ce faux comportement de construction par défaut en fournissant simplement un constructeur par défaut vide, ou en définissant le constructeur par défaut après l'avoir déclaré:
la source
Les réponses d' Angew et de JaggedSpire sont excellentes et s'appliquent àc ++ 11. Etc ++ 14. Etc ++ 17.
Cependant, dans c ++ 20, les choses changent un peu et l'exemple dans l'OP ne sera plus compilé:
Comme indiqué par les deux réponses, la raison pour laquelle les deux dernières déclarations fonctionnent est parce qu'il
C
s'agit d'un agrégat et qu'il s'agit d'une initialisation d'agrégat. Cependant, à la suite de P1008 (en utilisant un exemple motivant pas trop différent de l'OP), la définition de l'agrégat change dans C ++ 20 à, de [dcl.init.aggr] / 1 :Soulignez le mien. Désormais, il ne s'agit pas de constructeurs déclarés par l'utilisateur , alors qu'auparavant (comme les deux utilisateurs le citent dans leurs réponses et peuvent être consultés historiquement pour C ++ 11 , C ++ 14 et C ++ 17 ), aucun constructeur fourni par l'utilisateur . Le constructeur par défaut pour
C
est déclaré par l'utilisateur, mais pas fourni par l'utilisateur, et cesse donc d'être un agrégat dans C ++ 20.Voici un autre exemple illustratif de modifications globales:
B
n'était pas un agrégat en C ++ 11 ou C ++ 14 car il a une classe de base. En conséquence,B{}
appelle simplement le constructeur par défaut (déclaré par l'utilisateur mais pas fourni par l'utilisateur), qui a accès auA
constructeur par défaut protégé de.Dans C ++ 17, à la suite de P0017 , les agrégats ont été étendus pour permettre des classes de base.
B
est un agrégat en C ++ 17, ce qui signifie qu'ilB{}
s'agit d'une initialisation d'agrégat qui doit initialiser tous les sous-objets, y compris leA
sous - objet. Mais commeA
le constructeur par défaut est protégé, nous n'y avons pas accès, donc cette initialisation est mal formée.En C ++ 20, à cause du
B
constructeur déclaré par l'utilisateur, il cesse à nouveau d'être un agrégat, doncB{}
revient à appeler le constructeur par défaut et c'est à nouveau une initialisation bien formée.la source