Warum kann der klasseninterne C++11-Initialisierer keine Klammern verwenden?

Lesezeit: 6 Minuten

Warum kann der klasseninterne C11 Initialisierer keine Klammern verwenden
Delphizuerst

Ich kann z.B. nicht schreiben:

class A
{
    vector<int> v(12, 1);
};

Ich kann nur das schreiben:

class A
{
    vector<int> v1{ 12, 1 };
    vector<int> v2 = vector<int>(12, 1);
};

Was ist die Überlegung für die Unterschiede im C++11-Sprachdesign?

  • Ersteres ruft einen vector-Konstruktor auf, dessen Eingaben 12 und 1 sind. Letzteres ruft einen vector-Konstruktor auf, dessen Eingabe eine Initialisierungsliste ist. Sie sind grundlegend verschieden.

    – druckerman

    19. Juli 2014 um 3:50 Uhr

  • Der Grund für das Standardzitat liegt in der Grammatik Deklarator Klammer-oder-Gleich-Initialisierer (opt)

    – Chris

    19. Juli 2014 um 3:51 Uhr

Die Begründung für diese Wahl wird ausdrücklich in den verwandten erwähnt Vorschlag zum nicht statische Datenmember-Initialisierer :

Ein in Kona aufgeworfenes Problem bezüglich des Umfangs der Kennungen:

Während der Diskussion in der Kernarbeitsgruppe beim Treffen im September 2007 in Kona tauchte eine Frage über den Geltungsbereich von Bezeichnern im Initialisierer auf. Wollen wir den Klassenbereich mit der Möglichkeit der Vorwärtssuche zulassen? Oder möchten wir verlangen, dass die Initialisierer an dem Punkt, an dem sie analysiert werden, genau definiert sind?

Was ist erwünscht:

Die Motivation für die Klassenbereichssuche besteht darin, dass wir in der Lage sein möchten, alles in den Initialisierer eines nicht statischen Datenelements einzufügen, was wir in einen Mem-Initialisierer einfügen könnten, ohne die Semantik wesentlich zu ändern (Modulo-Direktinitialisierung vs. Kopierinitialisierung). :

int x();

struct S {
    int i;
    S() : i(x()) {} // currently well-formed, uses S::x()
    // ...
    static int x();
};

struct T {
    int i = x(); // should use T::x(), ::x() would be a surprise
    // ...
    static int x();
};

Problem 1:

Leider macht dies Initialisierer der Form „( Ausdrucksliste )“ zu dem Zeitpunkt mehrdeutig, zu dem die Deklaration analysiert wird:

   struct S {
        int i(x); // data member with initializer
        // ...
        static int x;
    };

    struct T {
        int i(x); // member function declaration
        // ...
        typedef int x;
    };

Eine mögliche Lösung besteht darin, sich auf die bestehende Regel zu verlassen, dass eine Deklaration eine Funktion ist, wenn sie ein Objekt oder eine Funktion sein könnte:

 struct S {
        int i(j); // ill-formed...parsed as a member function,
                  // type j looked up but not found
        // ...
        static int j;
    };

Eine ähnliche Lösung wäre die Anwendung einer anderen bestehenden Regel, die derzeit nur in Vorlagen verwendet wird, dass, wenn T ein Typ oder etwas anderes sein könnte, es etwas anderes ist; und wir können „typename“ verwenden, wenn wir wirklich einen Typ meinen:

struct S {
        int i(x); // unabmiguously a data member
        int j(typename y); // unabmiguously a member function
    };

Beide Lösungen führen Feinheiten ein, die wahrscheinlich von vielen Benutzern missverstanden werden (wie die vielen Fragen zu comp.lang.c++ zeigen, warum „int i();“ im Blockbereich kein standardmäßig initialisiertes int deklariert). .

Die in diesem Dokument vorgeschlagene Lösung besteht darin, nur Initialisierer der Formen „= initializer-clause“ und „{ initializer-list }“ zuzulassen. Das löst das Mehrdeutigkeitsproblem in die meisten Fälle, zum Beispiel:

HashingFunction hash_algorithm{"MD5"};

Hier konnten wir die Form = nicht verwenden, da der Konstruktor von HasningFunction explizit ist. In besonders kniffligen Fällen kann es vorkommen, dass ein Typ zweimal genannt werden muss. Erwägen:

   vector<int> x = 3; // error:  the constructor taking an int is explicit
   vector<int> x(3);  // three elements default-initialized
   vector<int> x{3};  // one element with the value 3

In diesem Fall müssen wir zwischen den beiden Alternativen wählen, indem wir die entsprechende Notation verwenden:

vector<int> x = vector<int>(3); // rather than vector<int> x(3);
vector<int> x{3}; // one element with the value 3

Problem 2:

Da wir keine Änderung der Regeln für die Initialisierung statischer Datenelemente vorschlagen, besteht ein weiteres Problem darin, dass das Hinzufügen des Schlüsselworts static einen wohlgeformten Initialisierer falsch formatieren könnte:

   struct S {
               const int i = f(); // well-formed with forward lookup
        static const int j = f(); // always ill-formed for statics
        // ...
        constexpr static int f() { return 0; }
    };

Problem 3:

Ein drittes Problem ist, dass die Klassenbereichssuche einen Kompilierzeitfehler in einen Laufzeitfehler umwandeln könnte:

struct S {
    int i = j; // ill-formed without forward lookup, undefined behavior with
    int j = 3;
};

(Sofern nicht vom Compiler abgefangen, wird i möglicherweise mit dem undefinierten Wert von j initialisiert.)

Der Antrag:

CWG hatte in Kona eine 6-zu-3-Strohumfrage zugunsten der Klassenbereichssuche; und genau das schlägt dieses Papier vor, wobei Initialisierer für nicht statische Datenelemente auf die Formen „= initializer-clause“ und „{ initializer-list }“ beschränkt sind.

Wir glauben:

Problem 1: Dieses Problem tritt nicht auf, da wir die ()-Notation nicht vorschlagen. Die Initialisierungsnotationen = und {} leiden nicht unter diesem Problem.

Problem 2: Das Hinzufügen des statischen Schlüsselworts macht eine Reihe von Unterschieden, dies ist der geringste davon.

Problem 3: Dies ist kein neues Problem, sondern das gleiche Problem der Initialisierungsreihenfolge, das bereits bei Konstruktor-Initialisierern besteht.

  • +1 für das Ausgraben und Formatieren für SO.

    – Prost und hth. – Alf

    19. Juli 2014 um 6:32 Uhr

Ein möglicher Grund ist, dass das Zulassen von Klammern uns zurück zu führen würde ärgerlichste Analyse im Handumdrehen. Betrachten Sie die beiden folgenden Typen:

struct foo {};
struct bar
{
  bar(foo const&) {}
};

Jetzt haben Sie einen Datenmember vom Typ bar die Sie initialisieren möchten, also definieren Sie es als

struct A
{
  bar B(foo());
};

Aber was Sie oben getan haben, ist eine Funktion namens zu deklarieren B das gibt a zurück bar Objekt nach Wert und nimmt ein einzelnes Argument, das eine Funktion mit der Signatur ist foo() (gibt a foo und nimmt keine Argumente entgegen).

Gemessen an der Anzahl und Häufigkeit der auf StackOverflow gestellten Fragen, die sich mit diesem Problem befassen, finden die meisten C++-Programmierer dies überraschend und unintuitiv. Neues hinzufügen Klammer-oder-Gleich-Initialisierer Syntax war eine Chance, diese Mehrdeutigkeit zu vermeiden und mit einer sauberen Weste zu beginnen, was wahrscheinlich der Grund dafür ist, dass sich das C++-Komitee dafür entschieden hat.

bar B{foo{}};
bar B = foo();

Beide Zeilen oben deklarieren ein Objekt namens B des Typs barwie erwartet.


Abgesehen von den obigen Vermutungen möchte ich darauf hinweisen, dass Sie in Ihrem obigen Beispiel zwei völlig unterschiedliche Dinge tun.

vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);

Die erste Zeile wird initialisiert v1 zu einem Vektor, der zwei Elemente enthält, 12 und 1. Die zweite erstellt einen Vektor v2 das beinhaltet 12 Elemente, jedes initialisiert auf 1.

Achten Sie auf diese Regel – wenn ein Typ einen Konstruktor definiert, der eine akzeptiert initializer_list<T>dann ist dieser Konstruktor immer zuerst berücksichtigt, wenn der Initialisierer für den Typ a ist geklammerte Init-Liste. Die anderen Konstrukteure werden nur dann berücksichtigt, wenn derjenige den übernimmt initializer_list ist nicht lebensfähig.

  • Bei Verwendung in der Parameterdeklaration foo() ist ein anderer Funktionszeiger als eine Funktion selbst, genau wie eine eingebaute Array-Deklaration.

    – Lingxi

    19. Juli 2014 um 5:34 Uhr

  • @Lingxi Habe ich das nicht auch gesagt?

    – Prätorianer

    19. Juli 2014 um 5:37 Uhr

  • Ich denke, Logik kann in Bezug auf kleine Details von C++ nicht zuverlässig leiten. Zum Beispiel logisch, da die Listeninitialisierung geschrieben werden kann v1{{12, 1}}die Bedeutung von v1{12,1} könnte ausgewählt werden, um gewöhnliche Konstruktoraufrufe zu unterstützen. Das wäre meine Wahl als Designer, beginnend hier mit „sauberem Blatt“. 😉

    – Prost und hth. – Alf

    19. Juli 2014 um 5:38 Uhr


  • @Praetorian In Ihrer ursprünglichen Aussage klingt es für mich etwas wie ein Hinweis auf die Funktion. Eigentlich kein großes Problem.

    – Lingxi

    19. Juli 2014 um 5:42 Uhr

  • Inwiefern ist das schlimmer als die ärgerlichste Analyse, die anderswo auftaucht?

    – Ben Voigt

    19. Juli 2014 um 5:43 Uhr

993110cookie-checkWarum kann der klasseninterne C++11-Initialisierer keine Klammern verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy