Pourquoi C ++ nécessite-t-il un constructeur par défaut fourni par l’utilisateur pour construire par défaut un object const?

Le standard C ++ (section 8.5) dit:

Si un programme demande l’initialisation par défaut d’un object de type T qualifié, T doit être un type de classe avec un constructeur par défaut fourni par l’utilisateur.

Pourquoi? Je ne peux penser à aucune raison pour laquelle un constructeur fourni par l’utilisateur est requirejs 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; } 

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 du groupe de travail central (CWG) 253 . Le nouveau libellé de la norme indique dans http://eel.is/c++draft/dcl.init#7

Un type de classe T est const-default-constructible si l’initialisation par défaut de T appelle un constructeur de T fourni par l’utilisateur (non hérité d’une classe de base) ou si

  • chaque membre de données non statique direct non variant M de T possède un initialiseur de membre par défaut ou, si M est de type X (ou son tableau), X est const-default-constructible,
  • si T est une union avec au moins un membre de données non statique, exactement un membre variant a un initialiseur de membre par défaut,
  • si T n’est pas une union, pour chaque membre d’union anonyme avec au moins un membre de données non statique (le cas échéant), exactement un membre de données non statique a un initialiseur de membre par défaut, et
  • chaque classe de base potentiellement construite de T est const-default-constructible.

Si un programme demande l’initialisation par défaut d’un object de type T qualifié, T doit être un type de classe const-default-constructible ou un tableau correspondant.

Cette formulation signifie essentiellement que le code évident fonctionne. Si vous initialisez toutes vos bases et membres, vous pouvez dire A const a; peu importe comment ou si vous épelez des constructeurs.

 struct A { }; A const a; 

gcc l’a accepté depuis 4.6.4. Clang a accepté cela depuis 3.9.0. Visual Studio accepte également cette solution (du moins en 2017, mais pas si tôt).

La raison en est que si la classe n’a pas de constructeur défini par l’utilisateur, il peut s’agir d’un POD et la classe POD n’est pas initialisée par défaut. Donc, si vous déclarez un object const de POD non initialisé, quelle en est l’utilisation? Je pense donc que la norme applique cette règle pour que l’object puisse réellement être utile.

 struct POD { int i; }; POD p1; //uninitialized - but don't worry we can assign some value later on! p1.i = 10; //assign some value later on! POD p2 = POD(); //initialized const POD p3 = POD(); //initialized const POD p4; //uninitialized - error - as we cannot change it later on! 

Mais si vous faites de la classe un non-POD:

 struct nonPOD_A { nonPOD_A() {} //this makes non-POD }; nonPOD_A a1; //initialized const nonPOD_A a2; //initialized 

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. Il y a plusieurs façons de le faire.

 struct nonPOD_B { virtual void f() {} //virtual function make it non-POD }; nonPOD_B b1; //initialized const nonPOD_B b2; //initialized 

Notez que nonPOD_B ne définit pas le constructeur défini par l’utilisateur. Comstackz le Il va comstackr:

Et commentez la fonction virtuelle, puis il y a erreur, comme prévu:


Eh bien, je pense que vous avez mal compris le passage. Il le dit d’abord (§8.5 / 9):

Si aucun initialiseur n’est spécifié pour un object et que l’object appartient à un type de classe non POD (ou à un tableau de celui-ci) (éventuellement qualifié de cv), l’object doit être initialisé par défaut; […]

Il parle de type non-POD possiblement qualifié de type cv . En d’autres termes, l’object 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),

Initialiser par défaut un object de type T signifie:
– si T est un type de classe non POD (clause 9), le constructeur par défaut de T est appelé (et l’initialisation est mal formée si T n’a pas de constructeur par défaut accessible);

Il parle simplement du constructeur par défaut de T, que le fichier défini par l’utilisateur ou le compilateur ne soit pas pertinent.

Si vous êtes clair à ce sujet, alors comprenez ce que dit la spécification suivante ((§8.5 / 9),

[…] Si l’object est de type qualifié, le type de classe sous-jacent doit avoir un constructeur par défaut déclaré par l’utilisateur.

Donc, ce texte implique que le programme sera mal formé si l’object est de type POD qualifié et qu’aucun initialiseur n’a été spécifié (car les POD ne sont pas initialisés par défaut):

 POD p1; //uninitialized - can be useful - hence allowed const POD p2; //uninitialized - never useful - hence not allowed - error 

Par ailleurs, cela comstack bien , car son non-POD, et peut être initialisé par défaut .

Pur spéculation de ma part, mais considère que d’autres types ont également une ressortingction similaire:

 int main() { const int i; // invalid } 

Donc, non seulement cette règle est cohérente, mais elle empêche également (récursivement) les objects const (sous) unifiés:

 struct X { int j; }; struct A { int i; X x; } int main() { const A a; // ai and axj in unitialized states! } 

Pour ce qui est de l’autre côté de la question (en autorisant 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é être toujours dans un état raisonnable après la construction. Notez que les règles telles qu’elles sont permettent:

 struct A { explicit A(int i): initialized(true), i(i) {} // valued constructor A(): initialized(false) {} bool initialized; int i; }; const A a; // class invariant set up for the object // yet we didn't pay the cost of initializing ai 

Alors peut-être pourrions-nous formuler une règle comme «au moins un membre doit être correctement initialisé dans un constructeur par défaut fourni par l’utilisateur», mais c’est trop de temps passé à essayer de se protéger contre Murphy. C ++ a tendance à faire confiance au programmeur sur certains points.

Félicitations, vous avez inventé un cas dans lequel il n’ya pas besoin de constructeur défini par l’utilisateur pour la déclaration const sans initialiseur.

Maintenant, pouvez-vous proposer une nouvelle formulation de la règle qui couvre votre cas, mais qui rend les cas illégaux illégaux? Est-ce moins de cinq ou six paragraphes? Est-ce facile et évident de voir comment cela doit être appliqué dans n’importe quelle situation?

Je pense que trouver une règle permettant à la déclaration que vous avez créée d’avoir du sens est vraiment difficile, et de s’assurer que la règle peut être appliquée d’une manière logique pour les personnes qui lisent du code est encore plus difficile. Je préférerais une règle quelque peu ressortingctive qui était la bonne chose à faire dans la plupart des cas à une règle très nuancée et complexe, difficile à comprendre et à appliquer.

La question est la suivante: existe-t-il une raison impérieuse pour laquelle la règle devrait être plus complexe? Existe-t-il un code qui serait autrement très difficile à écrire ou à comprendre et qui pourrait être écrit beaucoup plus simplement si la règle est plus complexe?