Le standard C ++ (section 8.5) dit:
Si un programme appelle l'initialisation par défaut d'un objet d'un type qualifié const T, T doit être un type de classe avec un constructeur par défaut fourni par l'utilisateur.
Pourquoi? Je ne vois aucune raison pour laquelle un constructeur fourni par l'utilisateur est requis dans ce cas.
struct B{
B():x(42){}
int doSomeStuff() const{return x;}
int x;
};
struct A{
A(){}//other than "because the standard says so", why is this line required?
B b;//not required for this example, just to illustrate
//how this situation isn't totally useless
};
int main(){
const A a;
}
a
, mais gcc-4.3.4 l'accepte même lorsque vous le faites (voir ideone.com/uHvFS )a
. Comeau produit une erreur "variable const" a "nécessite un initialiseur - la classe" A "n'a pas de constructeur par défaut explicitement déclaré" si la ligne est commentée.const A a{}
:)Réponses:
Cela a été considéré comme un défaut (par rapport à toutes les versions de la norme) et a été résolu par le défaut 253 du groupe de travail central (CWG) . Le nouveau libellé des états standard dans http://eel.is/c++draft/dcl.init#7
Cette formulation signifie essentiellement que le code évident fonctionne. Si vous initialisez toutes vos bases et tous vos membres, vous pouvez dire
A const a;
comment ou si vous épelez des constructeurs.gcc accepte cela depuis la version 4.6.4. clang a accepté cela depuis 3.9.0. Visual Studio accepte également cela (au moins en 2017, je ne sais pas si plus tôt).
la source
struct A { int n; A() = default; }; const A a;
tout en permettantstruct B { int n; B() {} }; const B b;
car le nouveau libellé dit toujours "fourni par l'utilisateur" et non "déclaré par l'utilisateur" et je me gratte la tête pourquoi le comité a choisi d'exclure de ce DR les constructeurs par défaut explicitement par défaut, nous obligeant à faire nos classes non triviales si nous voulons des objets const avec des membres non initialisés.MyPOD
tant que PODstruct
,static MyPOD x;
- en s'appuyant sur une initialisation à zéro (est-ce la bonne?) Pour définir la ou les variables membres de manière appropriée - compile, maisstatic const MyPOD x;
ne le fait pas. Y a-t-il une chance que cela soit corrigé?La raison en est que si la classe n'a pas de constructeur défini par l'utilisateur, alors il peut s'agir de POD et la classe POD n'est pas initialisée par défaut. Donc si vous déclarez un objet const de POD qui n'est pas initialisé, à quoi cela sert-il? Je pense donc que la norme applique cette règle afin que l'objet puisse réellement être utile.
Mais si vous faites de la classe un non-POD:
Notez la différence entre POD et non-POD.
Le constructeur défini par l'utilisateur est un moyen de rendre la classe non-POD. Vous pouvez le faire de plusieurs manières.
Remarquez que nonPOD_B ne définit pas de constructeur défini par l'utilisateur. Compilez-le. Il compilera:
Et commentez la fonction virtuelle, puis elle donne une erreur, comme prévu:
Eh bien, je pense que vous avez mal compris le passage. Il dit d'abord ceci (§8.5 / 9):
Il parle de classe non POD, éventuellement de type qualifié cv . Autrement dit, l'objet non POD doit être initialisé par défaut si aucun initialiseur n'est spécifié. Et qu'est-ce qui est initialisé par défaut ? Pour les non-POD, la spécification dit (§8.5 / 5),
Il parle simplement du constructeur par défaut de T, si son défini par l'utilisateur ou généré par le compilateur n'est pas pertinent.
Si vous êtes clair à ce sujet, alors comprenez ce que la spécification dit ensuite ((§8.5 / 9),
Donc, ce texte implique que le programme sera mal formé si l'objet est de type POD qualifié par const et qu'aucun initialiseur n'est spécifié (car les POD ne sont pas initialisés par défaut):
À propos, cela compile très bien , car il n'est pas POD et peut être initialisé par défaut .
la source
nonPOD_B
n'a pas de constructeur par défaut fourni par l'utilisateur, donc la ligneconst nonPOD_B b2
n'est pas autorisée.B
dans la question). Mais le constructeur par défaut fourni par l'utilisateur est toujours requis dans ce cas.const
aux objets non-POD d'être initialisés en appelant le constructeur par défaut généré par le compilateur.Pure spéculation de ma part, mais considérez que d'autres types ont également une restriction similaire:
Donc non seulement cette règle est cohérente, mais elle empêche également (récursivement)
const
(sous) :En ce qui concerne l'autre côté de la question (l'autorisant pour les types avec un constructeur par défaut), je pense que l'idée est qu'un type avec un constructeur par défaut fourni par l'utilisateur est censé toujours être dans un état raisonnable après la construction. Notez que les règles telles qu'elles sont permettent ce qui suit:
Ensuite, nous pourrions peut-être formuler une règle du type «au moins un membre doit être correctement initialisé dans un constructeur par défaut fourni par l'utilisateur», mais c'est beaucoup trop de temps passé à essayer de se protéger contre Murphy. C ++ a tendance à faire confiance au programmeur sur certains points.
la source
A(){}
, l'erreur disparaîtra, donc cela n'empêche rien. La règle ne fonctionne pas de manière récursive -X(){}
n'est jamais nécessaire pour cet exemple.Je regardais le discours de Timur Doumler à Meeting C ++ 2018 et j'ai finalement compris pourquoi la norme nécessite un constructeur fourni par l'utilisateur ici, pas simplement un constructeur déclaré par l'utilisateur. Cela concerne les règles d'initialisation des valeurs.
Considérez deux classes:
A
a un constructeur déclaréB
par l' utilisateur , a un constructeur fourni par l' utilisateur :À première vue, vous pourriez penser que ces deux constructeurs se comporteront de la même manière. Mais voyez comment l'initialisation de valeur se comporte différemment, alors que seule l'initialisation par défaut se comporte de la même manière:
A a;
est l'initialisation par défaut: le membreint x
n'est pas initialisé.B b;
est l'initialisation par défaut: le membreint x
n'est pas initialisé.A a{};
est l'initialisation de la valeur: le membreint x
est initialisé à zéro .B b{};
est l'initialisation de la valeur: le membreint x
n'est pas initialisé.Maintenant, voyez ce qui se passe lorsque nous ajoutons
const
:const A a;
est l'initialisation par défaut: elle est mal formée en raison de la règle citée dans la question.const B b;
est l'initialisation par défaut: le membreint x
n'est pas initialisé.const A a{};
est l'initialisation de la valeur: le membreint x
est initialisé à zéro .const B b{};
est l'initialisation de la valeur: le membreint x
n'est pas initialisé.Un
const
scalaire non initialisé (par exemple leint x
membre) serait inutile: y écrire est mal formé (parce qu'il estconst
) et lire à partir de celui-ci est UB (car il contient une valeur indéterminée). Donc cette règle vous empêche de créer une telle chose, en vous obligeant soit à ajouter un initialiseur ou opt-in au comportement dangereux en ajoutant un constructeur fourni par l' utilisateur.Je pense que ce serait bien d'avoir un attribut comme
[[uninitialized]]
dire au compilateur lorsque vous n'initialisez pas intentionnellement un objet. Ensuite, nous ne serions pas obligés de rendre notre classe non constructible par défaut pour contourner ce cas critique. Cet attribut a en fait été proposé , mais tout comme tous les autres attributs standard, il n'impose aucun comportement normatif, étant simplement un indice pour le compilateur.la source
Félicitations, vous avez inventé un cas dans lequel il n'y a pas besoin de constructeur défini par l'utilisateur pour que la
const
déclaration sans initialiseur ait un sens.Pouvez-vous maintenant proposer une reformulation raisonnable de la règle qui couvre votre cas tout en rendant les affaires qui devraient être illégales illégales? Est-ce moins de 5 ou 6 paragraphes? Est-il facile et évident de l'appliquer dans n'importe quelle situation?
Je suppose que trouver une règle qui permette à la déclaration que vous avez créée d'avoir un sens est vraiment difficile, et s'assurer que la règle peut être appliquée d'une manière qui a du sens pour les gens lors de la lecture du code est encore plus difficile. Je préférerais une règle quelque peu restrictive qui était la bonne chose à faire dans la plupart des cas à une règle très nuancée et complexe qui était difficile à comprendre et à appliquer.
La question est: y a-t-il une raison impérieuse pour laquelle la règle devrait être plus complexe? Y a-t-il un code qui serait autrement très difficile à écrire ou à comprendre qui pourrait être écrit beaucoup plus simplement si la règle est plus complexe?
la source
const POD x;
illégal tout commeconst int x;
c'est illégal (ce qui a du sens, car cela est inutile pour un POD), maisconst NonPOD x;
légal (ce qui a du sens, car il pourrait avoir des sous-objets contenant des constructeurs / destructeurs utiles, ou avoir un constructeur / destructeur utile lui-même) .struct NonPod { std::string s; }; const NonPod x;
et donne une erreur lorsque NonPod eststruct NonPod { int i; std::string s; }; const NonPod x;