Die "Uniform" Initialization besser vermeiden



  • Ich hoffe, dieses Forum hier wird in Zukunft von der so genannten "einheitlichen" Initialisierung in Zukunft abraten.

    Es gibt da viele Beispiele, weshalb dieses "einheitliche" ein Riesenschwindel ist.

    Zwar gibt es noch etliche ihr bezügliche Bugs im Standard (ein Fund von Kellerautomat, einer von Camper und viele mehr), die in Spezialfällen zu Problemen führen, was aber kein Grund ist, davon abzuraten.

    Vielmehr stört mich, dass die Semantik einfach kaputt ist.

    1. Als Einführung das Standardbeispiel mit std::vector<int>
    template <typename T> void f() { std::vector<T> v{42}; }
    

    Hier kann der Vektor die Grösse 42 oder 1 haben, abhängig davon, welche Konstruktoren T besitzt.

    1. Verschiedene Versuche, einen T zu kopieren:
    T kopie       {original};
    T copia    =  {original};
    T copiar   = T{original};
    auto copie = T{original};
    auto kopieer  {original};
    auto copy  =  {original};
    

    Alle sechs sind falsch, die ersten vier z.B. bei std::array , die letzten beiden machen etwas anderes.

    1. Wer toll findet, dass man jetzt Funktionsaufrufe vereinfachen kann:
    void f(std::string);
    f({"a"}); // Hach, wie kompakt das ist
    

    Das funktioniert allerdings schon nicht mehr, wenn die Funktion überladen wird:

    void f(std::string);
    void f(std::vector<std::string>);
    f({"a"}); // Knall
    

    Im Klartext heisst das, Funktionen nachträglich zu überladen macht fremden Code kaputt.

    1. Hier noch einmal das erste Beispiel:
    template <typename T> void f() { std::vector<T> v{42}; }
    

    Wenn std::vector eine eigene Klasse wäre, die noch keine std::initializer_list entgegen nimmt, ist der Code in Ordnung.
    Sobald eine hinzugefügt wird, ändert er sein Verhalten und das nicht zum Guten.
    Boost wie jede andere Bibliothek dürfte deshalb unter keinen Umständen einen Konstruktor mit einer std::initializer_list hinzufügen, weil sich jetzt schon Konstruktoren mit {} aufrufen lassen.

    Hier ein Ausschnitt von Bjarnes FAQ.

    X x{a};
    X* p = new X{a};
    z = X{a};
    f({a});
    return {a};
    

    Nehmen wir an, das ist generischer Template-Code trifft jedes der 4 Argumente trifft zu.
    Nehmen wir an, das ist normaler Code, dann treffen Argumente 1 und 2 manchmal zu, weshalb jedesmal kurz überlegt werden muss, ob das hier legal ist.
    Unabhängig davon gelten 3 und 4 IMMER, also gibt es hier nichts zu überlegen, denn es gilt: Die "Uniform" Initialization besser vermeiden.

    Einwände?



  • Sehr schön. Endlich mal jemand, der das Ganze ähnlich sieht wie ich.

    Hier meine Ausführungen, die erste davon vor ziemlich genau zwei Jahren:
    http://www.c-plusplus.net/forum/p2017868#2017868
    http://www.c-plusplus.net/forum/p2187510#2187510

    Einzelne Ausdrücke werden mit der Uniform Initialisation tatsächlich eleganter, daher würde ich nicht pauschal von der {} -Syntax abraten. Aber man sollte sich im Klaren sein, dass von "einheitlich" nicht wirklich die Rede sein kann.

    Man hätte meines Wissens viel mehr erreicht, wenn man {} nur für eigentliche Listen eingesetzt hätte. Dann wären keine Inkonsistenzen und Mehrdeutigkeiten aufgetreten. Aber momentan fühle ich mich lediglich an www.xkcd.com/927 erinnert...



  • Gut, das scheint hier schon angekommen zu sein (hab ich gar noch nicht gesehen). Jetzt steht es halt mehrfach hier im Forum.

    Nexus schrieb:

    daher würde ich nicht pauschal von der {} -Syntax abraten.

    Eigentlich kann man sie nur dann gefahrlos einsetzen, wenn man den Konstruktor mit der initializer_list verwenden möchte.
    Andererseits kann man keinen Konstruktor schreiben, der eine initializer_list annimmt, weil dann Code, der {} einsetzt, das Verhalten ändert.
    Der einzige Ausweg scheint mir zu sein, auf initializer_list und {...} zu verzichten, ausser wenn ein Standardcontainer mit einer Liste initialisiert werden soll oder mit der neuen for-Loop.
    Nur der Default-Konstruktor "{}" ist in Ordnung.

    Mit variadischen Templates hätte es aus meiner Sicht genügend bessere Alternativen gegeben, aber dann wäre C++ uneinheitlich und nicht mehr so anfängergerecht.



  • templer schrieb:

    aber dann wäre C++ uneinheitlich und nicht mehr so anfängergerecht.

    😮 🤡 👍



  • +1



  • abgesehen davon ist die Compilerunterstützung auch noch nicht komplett. laut C++11 Ref kann man eine braced-init-list auch zusammen mit op new verwenden, der MSVC mag diese Konstruktion aber nicht

    int * pi1 = new int { 3 };
      int * pi2 = new int [4] { 3, 4, 5, 6 };
    


  • dd++ schrieb:

    abgesehen davon ist die Compilerunterstützung auch noch nicht komplett. laut C++11 Ref kann man eine braced-init-list auch zusammen mit op new verwenden, der MSVC mag diese Konstruktion aber nicht

    int * pi1 = new int { 3 };
      int * pi2 = new int [4] { 3, 4, 5, 6 };
    

    najut, dass MSVC hinterhehingt, ist ja allgemein bekannt. 😃



  • aber dann wäre C++ uneinheitlich und nicht mehr so anfängergerecht.

    C++ ist wohl die allerletzte Sprache die anfängergerecht ist. C++ Code mit einem einfachen Interface hingegen gilt meistens als "schlecht". Hingegen ist Boost angeblich "gut".

    In anderen Sprachen bringt man nach 1 Woche nette Projekte auf die Beine. In C++ liegt man nach ner Woche höchstens wimmernd unter dem SChreibtisch, nachdem der Compiler bei einem kleinen Tippfehler in Templatecode mal wieder ein paar hundert Zeilen Fehlermeldungen rausgehauen hat.

    Nicht falsch verstehen, ich mag C++ gerade weil es freakig ist, aber wenn man ein anfängergerechtes C++ will, sollte man sich doch lieber D oder so ansehen.


Anmelden zum Antworten