Der C ++ - Standard (Abschnitt 8.5) lautet:
Wenn ein Programm die Standardinitialisierung eines Objekts eines const-qualifizierten Typs T anfordert, muss T ein Klassentyp mit einem vom Benutzer bereitgestellten Standardkonstruktor sein.
Warum? Ich kann mir keinen Grund vorstellen, warum in diesem Fall ein vom Benutzer bereitgestellter Konstruktor erforderlich ist.
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
, aber gcc-4.3.4 akzeptiert sie auch dann, wenn Sie dies tun (siehe ideone.com/uHvFS ).a
. Comeau erzeugt einen Fehler "const variable" a "erfordert einen Initialisierer - Klasse" A "hat keinen explizit deklarierten Standardkonstruktor", wenn die Zeile auskommentiert ist.const A a{}
:)Antworten:
Dies wurde als Fehler angesehen (gegenüber allen Versionen des Standards) und vom Defekt 253 der Core Working Group (CWG) behoben . Der neue Wortlaut für die Standardzustände in http://eel.is/c++draft/dcl.init#7
Dieser Wortlaut bedeutet im Wesentlichen, dass der offensichtliche Code funktioniert. Wenn Sie alle Ihre Basen und Mitglieder initialisieren, können Sie
A const a;
unabhängig davon sagen, wie oder ob Sie Konstruktoren buchstabieren.gcc hat dies seit 4.6.4 akzeptiert. clang hat dies seit 3.9.0 akzeptiert. Visual Studio akzeptiert dies ebenfalls (zumindest 2017, nicht sicher, ob früher).
quelle
struct A { int n; A() = default; }; const A a;
,struct B { int n; B() {} }; const B b;
da der neue Wortlaut immer noch "vom Benutzer bereitgestellt" und nicht "vom Benutzer deklariert" lautet und ich mich am Kopf kratzen muss, warum das Komitee explizit voreingestellte Standardkonstruktoren von dieser DR ausgeschlossen hat, was uns dazu zwingt Unsere Klassen sind nicht trivial, wenn wir const-Objekte mit nicht initialisierten Mitgliedern wollen.MyPOD
einem POD iststruct
,static MyPOD x;
- unter Berufung auf Null-Initialisierung (ist das der richtige?) , Um die Member - Variable zu setzen (s) in geeigneter Weise - kompiliert, aberstatic const MyPOD x;
nicht der Fall. Gibt es eine Chance, dass das behoben wird?Der Grund dafür ist, dass die Klasse, wenn sie keinen benutzerdefinierten Konstruktor hat, POD sein kann und die POD-Klasse nicht standardmäßig initialisiert wird. Wenn Sie also ein nicht initialisiertes const-Objekt von POD deklarieren, welchen Nutzen hat es dann? Daher denke ich, dass der Standard diese Regel erzwingt, damit das Objekt tatsächlich nützlich sein kann.
Aber wenn Sie die Klasse zu einem Nicht-POD machen:
Beachten Sie den Unterschied zwischen POD und Nicht-POD.
Der benutzerdefinierte Konstruktor ist eine Möglichkeit, die Klasse nicht POD zu machen. Es gibt verschiedene Möglichkeiten, dies zu tun.
Beachten Sie, dass nonPOD_B keinen benutzerdefinierten Konstruktor definiert. Kompiliere es. Es wird kompiliert:
Und kommentieren Sie die virtuelle Funktion, dann gibt es wie erwartet Fehler:
Nun, ich denke, Sie haben die Passage falsch verstanden. Es sagt zuerst dies (§8.5 / 9):
Es handelt sich um einen möglicherweise nicht lebenslaufqualifizierten Typ ohne POD-Klasse . Das heißt, das Nicht-POD-Objekt wird standardmäßig initialisiert, wenn kein Initialisierer angegeben ist. Und was ist standardmäßig initialisiert ? Für Nicht-POD heißt es in der Spezifikation (§8.5 / 5):
Es geht einfach um den Standardkonstruktor von T, unabhängig davon, ob sein benutzerdefinierter oder vom Compiler generierter Konstruktor irrelevant ist.
Wenn Sie sich darüber im Klaren sind, verstehen Sie, was in der Spezifikation als nächstes steht ((§8.5 / 9),
Dieser Text impliziert also, dass das Programm fehlerhaft ist, wenn das Objekt vom Typ POD mit konstanter Qualifikation ist und kein Initialisierer angegeben ist (da POD nicht standardmäßig initialisiert sind):
Übrigens wird dies gut kompiliert , da es kein POD ist und standardmäßig initialisiert werden kann .
quelle
nonPOD_B
gibt keinen vom Benutzer bereitgestellten Standardkonstruktor, daher ist die Zeileconst nonPOD_B b2
nicht zulässig.B
in der Frage). In diesem Fall ist der vom Benutzer bereitgestellte Standardkonstruktor weiterhin erforderlich.const
Nicht-POD-Objekte durch Aufrufen des vom Compiler generierten Standardkonstruktors zu initialisieren.Reine Spekulation meinerseits, aber bedenken Sie, dass auch andere Typen eine ähnliche Einschränkung haben:
Diese Regel ist also nicht nur konsistent, sondern verhindert auch (rekursiv) einheitliche
const
(Unter-) Objekte:Was die andere Seite der Frage betrifft (die es für Typen mit einem Standardkonstruktor zulässt), denke ich, dass ein Typ mit einem vom Benutzer bereitgestellten Standardkonstruktor nach der Konstruktion immer in einem vernünftigen Zustand sein sollte. Beachten Sie, dass die geltenden Regeln Folgendes zulassen:
Dann könnten wir vielleicht eine Regel formulieren wie "Mindestens ein Mitglied muss in einem vom Benutzer bereitgestellten Standardkonstruktor sinnvoll initialisiert werden", aber das ist viel zu viel Zeit, um sich vor Murphy zu schützen. C ++ vertraut dem Programmierer in bestimmten Punkten.
quelle
A(){}
wird der Fehler jedoch behoben, sodass nichts verhindert wird. Die Regel funktioniert nicht rekursiv -X(){}
wird für dieses Beispiel nie benötigt.Ich habe Timur Doumlers Vortrag auf dem Meeting C ++ 2018 gesehen und schließlich festgestellt, warum der Standard hier einen vom Benutzer bereitgestellten Konstruktor erfordert, nicht nur einen vom Benutzer deklarierten. Es hat mit den Regeln für die Wertinitialisierung zu tun.
Betrachten Sie zwei Klassen:
A
Hat einen vom Benutzer deklarierten Konstruktor,B
hat einen vom Benutzer bereitgestellten Konstruktor:Auf den ersten Blick könnte man meinen, dass sich diese beiden Konstruktoren gleich verhalten. Sehen Sie jedoch, wie sich die Wertinitialisierung anders verhält, während sich nur die Standardinitialisierung gleich verhält:
A a;
ist die Standardinitialisierung: Das Mitgliedint x
ist nicht initialisiert.B b;
ist die Standardinitialisierung: Das Mitgliedint x
ist nicht initialisiert.A a{};
ist Wertinitialisierung: Das Mitgliedint x
ist nullinitialisiert .B b{};
ist Wertinitialisierung: Das Mitgliedint x
ist nicht initialisiert.Nun sehen Sie, was passiert, wenn wir hinzufügen
const
:const A a;
ist die Standardinitialisierung: Dies ist aufgrund der in der Frage angegebenen Regel falsch .const B b;
ist die Standardinitialisierung: Das Mitgliedint x
ist nicht initialisiert.const A a{};
ist Wertinitialisierung: Das Mitgliedint x
ist nullinitialisiert .const B b{};
ist Wertinitialisierung: Das Mitgliedint x
ist nicht initialisiert.Ein nicht initialisierter
const
Skalar (z. B. dasint x
Mitglied) wäre nutzlos: Das Schreiben darauf ist schlecht geformt (weil es istconst
) und das Lesen daraus ist UB (weil es einen unbestimmten Wert enthält). Diese Regel verhindert also, dass Sie so etwas erstellen, indem Sie gezwungen werden, entweder einen Initialisierer hinzuzufügen oder sich für das gefährliche Verhalten zu entscheiden, indem Sie einen vom Benutzer bereitgestellten Konstruktor hinzufügen.Ich denke, es wäre schön, ein Attribut
[[uninitialized]]
zu haben, das dem Compiler mitteilt, wenn Sie absichtlich kein Objekt initialisieren. Dann wären wir nicht gezwungen, unsere Klasse nicht trivial standardmäßig konstruierbar zu machen, um diesen Eckfall zu umgehen. Dieses Attribut wurde tatsächlich vorgeschlagen , aber genau wie alle anderen Standardattribute erfordert es kein normatives Verhalten und ist lediglich ein Hinweis für den Compiler.quelle
Herzlichen Glückwunsch, Sie haben einen Fall erfunden, in dem es keinen benutzerdefinierten Konstruktor für die
const
Deklaration geben muss, ohne dass ein Initialisierer sinnvoll ist.Können Sie jetzt eine vernünftige Neuformulierung der Regel finden, die Ihren Fall abdeckt, aber dennoch die Fälle, die illegal sein sollten, illegal macht? Ist es weniger als 5 oder 6 Absätze? Ist es einfach und offensichtlich, wie es in jeder Situation angewendet werden sollte?
Ich gehe davon aus, dass es sehr schwierig ist, eine Regel zu entwickeln, mit der die von Ihnen erstellte Deklaration Sinn ergibt, und sicherzustellen, dass die Regel auf eine Weise angewendet werden kann, die für Menschen beim Lesen von Code sinnvoll ist. Ich würde eine etwas restriktive Regel, die in den meisten Fällen das Richtige war, einer sehr nuancierten und komplexen Regel vorziehen, die schwer zu verstehen und anzuwenden war.
Die Frage ist, gibt es einen zwingenden Grund, warum die Regel komplexer sein sollte? Gibt es einen Code, der sonst sehr schwer zu schreiben oder zu verstehen wäre und der viel einfacher geschrieben werden kann, wenn die Regel komplexer ist?
quelle
const POD x;
genauso illegal wieconst int x;
illegal (was sinnvoll ist, weil dies für einen POD nutzlos ist), aberconst NonPOD x;
legal (was sinnvoll ist, weil es Unterobjekte enthalten könnte, die nützliche Konstruktoren / Destruktoren enthalten, oder einen nützlichen Konstruktor / Destruktor selbst haben könnte). .struct NonPod { std::string s; }; const NonPod x;
und gibt einen Fehler, wenn NonPod iststruct NonPod { int i; std::string s; }; const NonPod x;