Warum unterstützt C ++ 11 bestimmte Initialisierungslisten nicht als C99? [geschlossen]


121

Erwägen:

struct Person
{
    int height;
    int weight;
    int age;
};

int main()
{
    Person p { .age = 18 };
}

Der obige Code ist in C99 legal, in C ++ 11 jedoch nicht.

Was war der Begründung des Standardausschusses, die Unterstützung für eine solche praktische Funktion auszuschließen?


10
Für das Designkomitee war es anscheinend nicht sinnvoll, es aufzunehmen, oder es kam einfach nicht in die Sitzungen. Es ist erwähnenswert, dass von C99 bezeichnete Initialisierer in keiner der C ++ - Spezifikationsversionen enthalten sind. Konstruktoren scheinen das bevorzugte Initialisierungskonstrukt zu sein, und das aus gutem Grund: Sie garantieren eine konsistente Objektinitialisierung, wenn Sie sie richtig schreiben.
Robert Harvey

19
Ihre Argumentation ist rückwärts, eine Sprache muss keine Begründung dafür haben , dass sie keine Funktion hat, sie muss eine Begründung dafür haben, dass sie eine und eine starke Funktion hat. C ++ ist in der jetzigen Form aufgebläht genug.
Matthieu M.

42
Ein guter Grund (der mit Konstruktoren nur durch das Schreiben von verblüffenden Wrappern gelöst werden kann) ist, dass unabhängig davon, ob Sie C ++ verwenden oder nicht, die meisten realen APIs C und nicht C ++ sind und nicht wenige von ihnen eine Struktur bereitstellen, in der Sie festlegen möchten ein oder zwei Felder - und nicht unbedingt das erste - müssen aber den Rest auf Null initialisiert werden. Die Win32-API OVERLAPPEDist ein solches Beispiel. In der Lage zu sein, zu schreiben, ={.Offset=12345};würde den Code viel klarer (und wahrscheinlich weniger fehleranfällig) machen. BSD-Sockel sind ein ähnliches Beispiel.
Damon

14
Der Code in mainist nicht legal C99. Es sollte lesen struct Person p = { .age = 18 };
chqrlie

14
FYI C ++ 20 wird bestimmte Initialisierer unterstützen
Andrew Tomazos

Antworten:


34

C ++ hat Konstruktoren. Wenn es sinnvoll ist, nur ein Mitglied zu initialisieren, kann dies im Programm durch Implementierung eines geeigneten Konstruktors ausgedrückt werden. Dies ist die Art von Abstraktion, die C ++ fördert.

Auf der anderen Seite geht es bei der Funktion für bestimmte Initialisierer eher darum, Mitglieder direkt im Client-Code verfügbar zu machen und ihnen den Zugriff zu erleichtern. Dies führt zu Dingen wie einer Person im Alter von 18 (Jahren?), Aber mit einer Größe und einem Gewicht von Null.


Mit anderen Worten, bestimmte Initialisierer unterstützen einen Programmierstil, bei dem Interna verfügbar gemacht werden, und der Client erhält die Flexibilität, zu entscheiden, wie er den Typ verwenden möchte.

C ++ ist mehr daran interessiert, die Flexibilität auf die Seite des Designers eines Typs zu legen, damit Designer es einfach machen können, einen Typ richtig und falsch zu verwenden. Dazu gehört, dass der Designer die Kontrolle darüber hat, wie ein Typ initialisiert werden kann: Der Designer bestimmt Konstruktoren, Initialisierer in der Klasse usw.


12
Bitte zeigen Sie einen Referenzlink für das, was Sie sagen, der Grund dafür ist, dass C ++ keine Initialisierer festgelegt hat. Ich kann mich nicht erinnern, jemals den Vorschlag dafür gesehen zu haben.
Johannes Schaub - litb

20
Ist nicht der eigentliche Grund, keinen Konstruktor dafür Personbereitzustellen, dass der Autor den Benutzern die größtmögliche Flexibilität beim Festlegen und Initialisieren der Mitglieder bieten wollte? Der Benutzer kann auch schon schreiben Person p = { 0, 0, 18 };(und das aus guten Gründen).
Johannes Schaub - litb

7
Ähnliches wurde kürzlich von open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.html in die C ++ 14-Spezifikation aufgenommen .
Johannes Schaub - litb

4
@ JohannesSchaub-litb Ich spreche nicht über die rein mechanische, unmittelbare Ursache (dh sie wurde dem Ausschuss nicht vorgeschlagen). Ich beschreibe, was meiner Meinung nach der dominierende Faktor ist. - Personhat ein sehr C-Design, daher können C-Funktionen sinnvoll sein. C ++ ermöglicht jedoch wahrscheinlich ein besseres Design, wodurch auch die Notwendigkeit ausgewiesener Initialisierer entfällt. - Meiner Ansicht nach entspricht die Aufhebung der Beschränkung für Klasseninitialisierer für Aggregate viel mehr dem Ethos von C ++ als den festgelegten Initialisierern.
Bames53

4
Der C ++ - Ersatz dafür könnte als Funktionsargumente bezeichnet werden. Derzeit existieren jedoch keine offiziellen Namensargumente. Siehe N4172 Benannte Argumente für einen Vorschlag hierfür. Dies würde den Code weniger fehleranfällig und leichter lesbar machen.
David Baird

89

Am 15. Juli 17 wurde P0329R4 in die aufgenommenStandard: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
Dies bringt begrenzte Unterstützung fürDesignated Initializers. Diese Einschränkung wird in C.1.7 [diff.decl] .4 wie folgt beschrieben:

struct A { int x, y; };
struct B { struct A a; };

Die folgenden designierten Initialisierungen, die in C gültig sind, sind in C ++ eingeschränkt:

  • struct A a = { .y = 1, .x = 2 } ist in C ++ ungültig, da Bezeichner in der Deklarationsreihenfolge der Datenelemente erscheinen müssen
  • int arr[3] = { [1] = 5 } ist in C ++ ungültig, da die vom Array festgelegte Initialisierung nicht unterstützt wird
  • struct B b = {.a.x = 0} ist in C ++ ungültig, da Bezeichner nicht verschachtelt werden können
  • struct A c = {.x = 1, 2} ist in C ++ ungültig, da entweder alle oder keine der Datenelemente von Bezeichnern initialisiert werden müssen

Zum und früher hat Boost tatsächlich Unterstützung für Designated Intializers und es gab zahlreiche Vorschläge, um die Unterstützung für die hinzuzufügenStandard, zum Beispiel: n4172 und Daryle Walkers Vorschlag, Initialisierern eine Bezeichnung hinzuzufügen . Die Vorschläge zitieren die Umsetzung vonDesignated Initializers in Visual C ++, gcc und Clang behaupten:

Wir glauben, dass die Änderungen relativ einfach umzusetzen sein werden

Das Standardkomitee lehnt solche Vorschläge jedoch wiederholt ab und erklärt:

Die EWG stellte verschiedene Probleme mit dem vorgeschlagenen Ansatz fest und hielt es nicht für möglich, das Problem zu lösen, da es viele Male versucht wurde und jedes Mal fehlschlug

Die Kommentare von Ben Voigt haben mir geholfen, die unüberwindlichen Probleme mit diesem Ansatz zu erkennen. gegeben:

struct X {
    int c;
    char a;
    float b;
};

In welcher Reihenfolge würden diese Funktionen aufgerufen? : struct X foo = {.a = (char)f(), .b = g(), .c = h()}? Überraschenderweise in::

Die Reihenfolge der Auswertung der Unterausdrücke in einem Initialisierer ist unbestimmt geordnet [ 1 ]

(Visual C ++, gcc und Clang scheinen ein vereinbartes Verhalten zu haben, da sie alle die Anrufe in dieser Reihenfolge tätigen werden :)

  1. h()
  2. f()
  3. g()

Die Unbestimmtheit des Standards bedeutet jedoch, dass bei einer Interaktion dieser Funktionen auch der resultierende Programmstatus unbestimmt wäre und der Compiler Sie nicht warnen würde : Gibt es eine Möglichkeit, sich vor schlecht benommenen designierten Initialisierern zu warnen ?

hat strenge Anforderungen an die Initialisierungsliste 11.6.4 [dcl.init.list] 4:

Innerhalb der Initialisiererliste einer Klammer-Init-Liste werden die Initialisierer-Klauseln, einschließlich aller Klauseln, die sich aus Pack-Erweiterungen (17.5.3) ergeben, in der Reihenfolge ausgewertet, in der sie erscheinen. Das heißt, jede Wertberechnung und jeder Nebeneffekt, die einer bestimmten Initialisierungsklausel zugeordnet sind, wird vor jeder Wertberechnung und jedem Nebeneffekt, der einer Initialisierungsklausel zugeordnet ist, die darauf folgt, in der durch Kommas getrennten Liste der Initialisierungsliste sequenziert.

So Für den Support wäre dies in der folgenden Reihenfolge erforderlich gewesen:

  1. f()
  2. g()
  3. h()

Kompatibilität mit früheren brechen Implementierungen.
Wie oben erläutert, wurde dieses Problem durch die Einschränkungen für Designated Initializers umgangen, die in akzeptiert wurden. Sie bieten ein standardisiertes Verhalten und garantieren die Ausführungsreihenfolge der Designated Initializers.


3
Sicher, in diesem Code: struct X { int c; char a; float b; }; X x = { .a = f(), .b = g(), .c = h() };Der Aufruf von h()wird vor entweder f()oder ausgeführt g(). Wenn die Definition von struct Xnicht in der Nähe ist, wird dies sehr überraschend sein. Denken Sie daran, dass Initialisiererausdrücke nicht nebenwirkungsfrei sein müssen.
Ben Voigt

2
Dies ist natürlich nichts Neues. Die Initialisierung von ctor-Mitgliedern hat dieses Problem bereits, aber es ist in der Definition eines Klassenmitglieds enthalten, sodass eine enge Kopplung keine Überraschung ist. Und bestimmte Initialisierer können die anderen Mitglieder nicht so referenzieren wie die ctor-Mitgliedsinitialisierer.
Ben Voigt

2
@ MattMcNabb: Nein, es ist nicht extremer. Man erwartet jedoch, dass der Entwickler, der den Klassenkonstruktor implementiert, die Reihenfolge der Mitgliederdeklarationen kennt. Während der Verbraucher der Klasse ein völlig anderer Programmierer sein könnte. Da es darum geht, eine Initialisierung zu ermöglichen, ohne die Reihenfolge der Mitglieder nachschlagen zu müssen, scheint dies ein schwerwiegender Fehler im Vorschlag zu sein. Da bestimmte Initialisierer nicht auf das zu erstellende Objekt verweisen können, besteht der erste Eindruck darin, dass Initialisierungsausdrücke zuerst in der Reihenfolge der Bezeichnung und dann in der Reihenfolge der Deklaration der Elemente ausgewertet werden können. Aber ...
Ben Voigt

2
@JonathanMee: Nun, die andere Frage hat beantwortet, dass ... C99-Aggregatinitialisierer ungeordnet sind, sodass nicht erwartet wird, dass bestimmte Initialisierer bestellt werden. C ++ - Klammer-Init-Listen sind geordnet, und der Vorschlag für bestimmte Initialisierer verwendet eine möglicherweise überraschende Reihenfolge (Sie können nicht sowohl mit der lexikalischen Reihenfolge, die für alle Klammer-Init-Listen verwendet wird, als auch mit der Mitgliedsreihenfolge, die für den Ctor-Initialisierer verwendet wird, übereinstimmen -Listen)
Ben Voigt

3
Jonathan: "Für die Unterstützung von C ++ wäre es erforderlich gewesen, dass dies in der Reihenfolge ausgeführt wird, in der [...] die Kompatibilität mit früheren c99-Implementierungen unterbrochen wurde." Ich verstehe das nicht, sorry. 1. Wenn die Reihenfolge in C99 unbestimmt ist, sollte natürlich jede tatsächliche Reihenfolge in Ordnung sein, einschließlich einer beliebigen C ++ - Auswahl. b) Das Des nicht unterstützen. Initialisierer brechen die C99-Kompatibilität schon ein bisschen mehr ...
Gr.

34

Ein bisschen Hackery, also nur zum Spaß teilen.

#define with(T, ...)\
    ([&]{ T ${}; __VA_ARGS__; return $; }())

Und benutze es wie:

MyFunction(with(Params,
    $.Name = "Foo Bar",
    $.Age  = 18
));

was erweitert zu:

MyFunction(([&] {
 Params ${};
 $.Name = "Foo Bar", $.Age = 18;
 return $;
}()));

Ordentlich, erstellt ein Lambda mit einer Variablen $vom Typ Typ T, und Sie weisen seine Mitglieder direkt zu, bevor Sie es zurückgeben. Raffiniert. Ich frage mich, ob es irgendwelche Leistungsprobleme gibt.
TankorSmash

1
In einem optimierten Build sehen Sie weder Spuren des Lambda noch dessen Aufruf. Es ist alles inline.
Keebus

1
Ich liebe diese Antwort absolut.
Seph Reed

6
Woah. Ich wusste nicht einmal, dass $ ein gültiger Name ist.
Chris Watts

Es wurde von älteren C-Compilern unterstützt und aus Gründen der Abwärtskompatibilität beibehalten.
Keebus

22

Bestimmte Initialisierer sind derzeit in C ++ 20 enthalten: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf, damit wir sie endlich sehen können!


3
Beachten Sie jedoch, dass sie eingeschränkt sind: In C ++ ist die Unterstützung für die designierte Initialisierung im Vergleich zu den entsprechenden Funktionen in C eingeschränkt. In C ++ müssen Bezeichner für nicht statische Datenelemente in der Deklarationsreihenfolge angegeben werden, Bezeichner für Array-Elemente und verschachtelte Bezeichner nicht Unterstützte und bestimmte und nicht festgelegte Initialisierer können nicht in derselben Initialisiererliste gemischt werden. Dies bedeutet, dass Sie insbesondere immer noch nicht einfach eine Nachschlagetabelle mit Enum-Key erstellen können .
Ruslan

@ Ruslan: Ich frage mich, warum C ++ sie so stark eingeschränkt hat? Ich verstehe, dass es Verwirrung darüber geben kann, ob die Reihenfolge, in der die Werte von Elementen ausgewertet und / oder in die Struktur geschrieben werden, mit der Reihenfolge übereinstimmt, in der Elemente in der Initialisierungsliste angegeben sind, oder mit der Reihenfolge, in der Mitglieder in der Struktur erscheinen, aber die Lösung das wäre einfach zu sagen , dass Initialisierungsausdrücken in beliebiger Reihenfolge ausgeführt werden, und die Lebensdauer des Objekts beginnt nicht , bis die Initialisierung abgeschlossen ist (der &Bediener die Adresse zurückkommen würde , dass das Objekt wird während seiner gesamten Lebensdauer hat).
Supercat

5

Zwei Kernfunktionen von C99 , die in C ++ 11 fehlen, erwähnen „Designated Initializers and C ++“.

Ich denke, der "designierte Initialisierer" bezog sich auf eine mögliche Optimierung. Hier verwende ich als Beispiel "gcc / g ++" 5.1.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>    
struct point {
    int x;
    int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

Wir wussten zur Kompilierungszeit, dass a_point.xNull ist, also konnten wir erwarten, dass dies foozu einer einzigen optimiert wird printf.

$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
   0x00000000004004f0 <+0>: sub    $0x8,%rsp
   0x00000000004004f4 <+4>: mov    $0x4005bc,%edi
   0x00000000004004f9 <+9>: xor    %eax,%eax
   0x00000000004004fb <+11>:    callq  0x4003a0 <printf@plt>
   0x0000000000400500 <+16>:    xor    %eax,%eax
   0x0000000000400502 <+18>:    add    $0x8,%rsp
   0x0000000000400506 <+22>:    retq   
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc:   "x == 0"

fooist x == 0nur für den Druck optimiert .

Für die C ++ - Version

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
    point(int _x,int _y):x(_x),y(_y){}
    int x;
    int y;
};
const struct point a_point(0,0);
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

Und dies wird vom optimierten Assemble-Code ausgegeben.

g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>:    push   %rbx
0x00000000004005c1 <+1>:    mov    0x200489(%rip),%ebx        # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>:    test   %ebx,%ebx
0x00000000004005c9 <+9>:    je     0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>:   mov    $0x1,%ebx
0x00000000004005d0 <+16>:   mov    $0x4006a3,%edi
0x00000000004005d5 <+21>:   xor    %eax,%eax
0x00000000004005d7 <+23>:   callq  0x400460 <printf@plt>
0x00000000004005dc <+28>:   mov    %ebx,%eax
0x00000000004005de <+30>:   pop    %rbx
0x00000000004005df <+31>:   retq   
0x00000000004005e0 <+32>:   mov    $0x40069c,%edi
0x00000000004005e5 <+37>:   xor    %eax,%eax
0x00000000004005e7 <+39>:   callq  0x400460 <printf@plt>
0x00000000004005ec <+44>:   mov    %ebx,%eax
0x00000000004005ee <+46>:   pop    %rbx
0x00000000004005ef <+47>:   retq   

Wir können sehen, dass dies a_pointnicht wirklich ein Wert für die Kompilierungszeitkonstante ist.


8
Jetzt versuchen Sie es bitte constexpr point(int _x,int _y):x(_x),y(_y){}. Der Optimierer von clang ++ scheint den Vergleich auch in Ihrem Code zu eliminieren. Dies ist also nur ein QoI-Problem.
Dyp

Ich würde auch erwarten, dass das gesamte a_point-Objekt weg optimiert wird, wenn es eine interne Verknüpfung hat. dh legen Sie es in den anonymen Namespace und sehen Sie, was passiert. goo.gl/wNL0HC
Arvid

@dyp: Selbst das Definieren eines Konstruktors ist nur möglich, wenn der Typ unter Ihrer Kontrolle steht. Sie können dies beispielsweise nicht für struct addrinfooder tun struct sockaddr_in, sodass Sie Aufgaben haben, die von Deklarationen getrennt sind.
Musiphil

2
@musiphil Zumindest in C ++ 14 können diese Strukturen im C-Stil mithilfe einer Zuweisung in einer constexpr-Funktion ordnungsgemäß als lokale Variablen eingerichtet und dann von dieser Funktion zurückgegeben werden. Außerdem wollte ich keine alternative Implementierung des Konstruktors in C ++ zeigen, die die Optimierung ermöglicht, sondern zeigen, dass der Compiler diese Optimierung durchführen kann, wenn die Form der Initialisierung unterschiedlich ist. Wenn der Compiler "gut genug" ist (dh diese Form der Optimierung unterstützt), sollte es irrelevant sein, ob Sie einen ctor oder bestimmte Initialisierer oder etwas anderes verwenden.
Dyp
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.