Frage - Klasseninitialisierung


  • Mod

    pumuckl schrieb:

    Es muss nicht unbedingt nur ein Argument sein. Man kann auch mehrere Argumente haben, wenn es fuer die zusaetlichen defaultwerte gibt. Zumindest mein gcc hat folgendes geschluckt:

    Wahr, allerdings als solches kein Widerspruch zum zuvor ausgeführten. Allerdings hat mich dein Beispiel daran erinnert, dass meine Erklärung für explicit fehlerhaft war:

    Foo f4 = i;
    

    Das geht hier, aber der Konstruktor, der aufgerufen wird, ist Foo::Foo(float, float). Dagegen wird f3 durch den Konstruktor Foo::Foo(int, float) konstruiert.
    Korrektur Nr. 1: Die Auswahl eines explicit Konstruktors während einer impliziten Konvertierung stellt keinen Fehler dar - ein solcher Konstruktor ist von vornherein kein Konvertierungskonstruktor und wird gar nicht erst berücksichtigt (und es ist dann ggf. ein Fehler, dass keine Konvertierungsmöglichkeit gefunden wird). Der Unterschied wird in dem Beispiel deutlich: Foo::Foo(int, float) wäre eigentlich die bessere Überladung, und würde gewählt werden, wenn explicit erst nach Überladungsauflösung berücksichtigt würde. So wird der Konstruktor Foo(int, float) aber gar nicht erst Teil des Überladungssets.
    Korrektur Nr. 2: explicit verhindert nicht nur die Benutzung bei impliziter Konvertierung - ein solcher Konstruktor darf überhaupt nur dort verwendet werden, wo die syntaktische Form der direkten Initialisierung gewählt wird. Das wirkt sich primär bei expliciten Kopierkonstruktoren aus:

    zum Nachdenken, welche der Folgenden Initialisierungen ist fehlerhaft:

    struct Foo { Foo(); Foo(int); explicit Foo(const Foo&); };
    struct Bar { Bar(); explicit Bar(int); Bar(const Bar&); };
    int main()
    {
        int i = 0; Foo x; Bar y;
        Foo x1(i);
        Foo x2(x);
        Foo x3 = i;
        Foo x4 = x;
        const Foo& x5 = i;
        const Foo& x6 = x;
        Bar y1(i);
        Bar y2(y);
        Bar y3 = i;
        Bar y4 = y;
        const Bar& y5 = i;
        const Bar& y6 = y;
    }
    


  • Sry, das ich erst wieder so spät schreibe, hat grad keine Zeit. Mh also was ich bisher mal sicher gecheckt habe ist, das wenn der Konstruktor "explicite" ist, dann gehen nur noch solche "copy initializations" --> Foo temp=Foo(20); und solche nicht mehr --> Foo temp=20;

    #include <iostream>
    using namespace std;
    
    class Foo
    {
        public:
        explicit Foo(int save_);
        ~Foo();
    
        private:
        int m_save;
    };
    
    Foo::Foo(int save_)
    :m_save(save_)
    {
    
    }
    
    Foo::~Foo()
    {
    
    }
    
    int main()
    {
        Foo X1=Foo(20); //GEHT
        Foo X2=20; //COMPILIERERFEHLER
    
        return 0;
    }
    

    Aber ich verstehe trotzdem den Unterschied zwischen "Foo X1=Foo(20);" und "Foo X2=20" nicht. Okay, ich weiß dass das erstere [Foo X1=Foo(20);] "explizit" ist, und das letztere [Foo X2=20;] "implizit" ist. Soweit okay, aber die machen doch beide das gleiche oder? "Foo X2=20;" deklariert ein "temp(20)" Objekt (oder halt irgend n anderer Name...), und übergibt diese über den Kopierkonstruktor (an X2).
    Ja und "Foo X1=Foo(20)" macht doch genau das selbe? Es wird wieder ein "temp(20)" Objekt erstellt, und dann über den Kopierkonstruktor übergeben (an X1).
    Der einzige Unterschied ist doch also nur, das ich bei "Foo(20)" noch mehr Arguemnte übergeben kann, also z.B. "Foo X1=Foo(20,4,5,29,4);". [Also der Konstruktor von der Klasse Foo muss natürlich entsprechend viele Parameter haben....is ja selbstverständlich.]
    Aber irgendwie blick ich so den großen Unterschied noch nicht?! Oder ist es gar nicht so wie ich gesagt habe?

    MfG
    Stromberg

    PS: Ich schau mir gleich mal die Übung an.



  • Der Unterschied liegt zwischen "explizit" und "implizit" - im ersten Fall sagst du dem Compiler, daß du ein Foo-Objekt mit dem int-Ctor angelegt haben willst, im letzteren Fall sagst du ihm, daß er irgendwie versuchen soll, den int-Wert in ein Foo umzuwandeln (und dafür stehen ihm nur implizite Umwandlungen zur Verfügung, also nicht der angegebene Ctor).



  • CStoll schrieb:

    ...und dafür stehen ihm nur implizite Umwandlungen zur Verfügung...

    Und was wären das das dann für Umwandlungen? Wie sieht das dann aus, was macht dann der Compiler mit dem Code?

    Ist "implizit" schneller, oder ist "explicit" schneller? Weil wenn z.B. "explicit" schneller wäre, dann könnte man ja nur imemr das verwenden...oder?

    MfG
    Stromberg



  • Stromberg schrieb:

    CStoll schrieb:

    ...und dafür stehen ihm nur implizite Umwandlungen zur Verfügung...

    Und was wären das das dann für Umwandlungen? Wie sieht das dann aus, was macht dann der Compiler mit dem Code? ...

    Implizite Umwandlungen bekommst du z.B. wenn du einen short-Wert einem int-Parameter übergibst. Explizit bei einem Konstruktor heißt, das dieser keine impliziten Umwandlung vornehmen darf. Sprich wenn du einer Klasse nur einem expliziten Konstruktor mit einem int-Wert als Parameter gibst, kannst du diese nicht mit einem short-Wert konstruieren.

    Explizite Konstruktoren sind in der Regel den impliziten vorzusehen wenn es um Typsicherheit geht.

    cu André



  • Achso!!!! Jetzt wird mir einges klar, löl 2 Tage tue ich rum bis ichs checke. Okay, dann versteh ichs jetzt, is ja eigentlich ganz einfach.
    Mh, aber nochmal zur Schreibweise, ich weiß ja jetzt dass das hier... -->

    class Foo;
    
    int main()
    {
        int temp
        Foo X=Foo(temp); //...explizit ist!
        return 0;
    }
    

    und das hier:

    class Foo;
    
    int main()
    { 
        int temp;
        Foo X=temp; //...implizit ist!
        return 0;
    }
    

    1. Gibt es für "implizit" noch eine Andere Form? Weil wie schön des öfteren gesagt, verstehe ich das sonst nicht, weil sonst kann man mit einer "implizit" copy initialization ja nur einen Parameter abdecken. Daraus folgt ja dann wohl, die Klasse die "implizit" copy initializiert wird, darf nur ein Argument im Parameter haben, bzw. müssen sonst die anderen alles einen default Parameter haben. Ist das korrekt? Oder gibts noch eine andere "implizit Form"?
    2.

    CStoll schrieb:

    ...im letzteren Fall sagst du ihm, daß er irgendwie versuchen soll, den int-Wert in ein Foo umzuwandeln...

    Das ist ja eigentlich das was "asc" angesprochen hat. Mit dem "short" an ein "int" übergeben. Findet hier also so eine art "static_cast" statt? Jetzt mal aus reinem Interesse, wie funktioniert das eigentlich im "hintergrund"? genauso wie beim expliziten, das erst ein "temp Objekt" erstellt wird, das dann irgendwie mit ner Umwandlung (static_cast) übergeben wird....? Weiß da jemand wie das gemacht wird?
    3. Eigentlich verwendet man copy initializations, ob "implizit" oder "explizit" doch eher selten? Meistens nimmt man doch immer noch den Konstruktor oder? Zumal der doch auch viel schneller ist (direct_initialization).

    Dankeschön schon mal im Voraus.
    MfG
    Stromberg



  • Stromberg schrieb:

    1. Gibt es für "implizit" noch eine Andere Form? Weil wie schön des öfteren gesagt, verstehe ich das sonst nicht, weil sonst kann man mit einer "implizit" copy initialization ja nur einen Parameter abdecken. Daraus folgt ja dann wohl, die Klasse die "implizit" copy initializiert wird, darf nur ein Argument im Parameter haben, bzw. müssen sonst die anderen alles einen default Parameter haben. Ist das korrekt? Oder gibts noch eine andere "implizit Form"?

    Impliziter Konstruktoraufruf ist immer dann möglich, wenn es nur ein Parameter (+beliebig viele weitere mit Defaultargumenten) gibt. Eine Implizite Konvertierung kann aber mit beliebig vielen Parametern stattfinden (z.B. wenn der dritte Parameter ein int verlangt, du aber ein short übergibst).

    Stromberg schrieb:

    2. Das ist ja eigentlich das was "asc" angesprochen hat. Mit dem "short" an ein "int" übergeben. Findet hier also so eine art "static_cast" statt?...

    Ja, der Compiler darf glaube ich auch 2 implizite Konvertierungen in Folge durchführen um auf ein Wert zu kommen... Sprich wenn du eine Klasse a hast, die im Konstruktor ein Argument der Klasse b verlangt, diese wieder einen Konstruktor für int hat, wird der Aufruf des Konstruktors der Klasse a mit einem int-Wert funktionieren... Was sehr gefährlich, und der eigentliche Grund für die explizite Konstruktordeklaration ist.

    Stromberg schrieb:

    3. Eigentlich verwendet man copy initializations, ob "implizit" oder "explizit" doch eher selten? Meistens nimmt man doch immer noch den Konstruktor oder? Zumal der doch auch viel schneller ist (direct_initialization).

    Auch beim impliziten Konstruktoraufruf wird dennoch der Konstruktor verwendet.

    cu André



  • asc schrieb:

    ...Explizit bei einem Konstruktor heißt, das dieser keine impliziten Umwandlung vornehmen darf. Sprich wenn du einer Klasse nur einem expliziten Konstruktor mit einem int-Wert als Parameter gibst, kannst du diese nicht mit einem short-Wert konstruieren.

    Explizite Konstruktoren sind in der Regel den impliziten vorzusehen wenn es um Typsicherheit geht......

    Warum geht das hier dann?

    #include <iostream>
    using namespace std;
    
    class Foo
    {
        public:
        explicit Foo(int a1_);
        ~Foo();
    
        private:
        int m_a1;
    
    };
    
    Foo::Foo(int a1_)
    :m_a1(a1_)
    {
    
    }
    
    Foo::~Foo()
    {
    
    }
    
    int main()
    {
        short x=13;
        Foo test=Foo(x);
    
        return 0;
    }
    

    MfG
    Stromberg



  • Weil asc dort etwas falsches geschrieben hat - 'explicit' verhindert nicht die internen Typumwandlungen von C++, es verhindert nur die implizite Umwandlung von int nach foo (in deinem Beispiel), d.h. die Verwendung foo x=10; oder die Verwendung eines int-Wertes für einen foo.

    @asc: Und es gibt zwar mitunter mehrere implizite Umwandlungen hintereinander, aber davon maximal eine benutzerdefinierte (Konstruktor oder Umwandlungsoperator).



  • Die explizite Typumwandlung verhindert (so wie CStoll schon schrieb) die automatische Typumwandlung bei benutzerdefineirten Typen.
    Sie kommt erst dann wirklich zur Geltung, wenn mehrere Klassen im Spiel sind, z.B.

    class Foo
    {
        explicit Foo(int a);
    };
    
    class Bar
    {
    public:
        Bar(const Foo &foo);
    };
    
    int main()
    {
       Bar b = 42; // dies führt jetzt zu einem Fehler!
    }
    

    Ohne die Angabe von 'explicit' bei Foo würde die obere Anweisung vom Compiler als

    Bar b = Bar(Foo(42)); // bzw. dies ist gleichwertig mit: Bar b(Foo(42))
    

    interpretiert.

    P.S: Stroustrup gibt in seinem Buch auch noch als Beispiel:

    // selbsterstellte Klasse
    class String
    {
      explicit String(int n); // Vorbelegung von n Zeichen
    };
    
    String s(10); // Initialisierung mit 10 Zeichen
    String s('a'); // <- Fehler, ohne explicit würden 65 Zeichen vorbelegt
    

    Eine sogenannte Promotion ist dagegen immer möglich, also von short<->int<->long.



  • Interessanterweise sollte das der Compiler auch ohne explicit abweisen - es ist maximal eine nutzerdefinierte implizite Umwandlung am Stück erlaubt. (Edit: Und so einen Ansatz habe ich schon als Workaround gesehen für Compiler, die nichts mit explicit anfangen können)

    Hier mal ein Beispiel, wo es wirklich einen Unterschied macht:

    class test
    {
    public:
      explicit test(int);
    private:
      ...
    };
    
    void print(const test& value);
    
    ...
    print(4711);      //verboten - Umwandlung ist explizit
    print(test(4711));//erlaubt
    ...
    //und hier der kleine, aber feine Unterschied:
    test i = 100;     //verboten
    test i(100);      //erlaubt
    


  • Das hier funktioniert bei mir nicht, mit "explicit" oder ohne "explicit":

    #include <iostream>
    using namespace std;
    
    class Foo
    {
        public:
        explicit Foo(int a);
        private:
        int m_x;
    };
    
    Foo::Foo(int a)
    :m_x(a)
    {
    
    }
    
    class Bar
    {
        public:
        Bar(const Foo &foo);
        private:
        Foo m_y;
    };
    
    Bar::Bar(const Foo &foo)
    :m_y(foo)
    {
    
    }
    
    int main()
    {
       Bar b = 42; // dies führt jetzt zu einem Fehler!
    }
    

    Außerdem wäre für den Compiler "Bar b = 42" u. "Bar b = Foo(42)" immer --> Bar b = Bar(Foo(42));???

    Und was ist jetzt eigentlich nochmal die Poente von "explicit"? Was soll damit verhindert bzw. bezweckt werden? Wo liegt der Sinn?

    MfG
    Stromberg



  • Stromberg schrieb:

    Und was ist jetzt eigentlich nochmal die Poente von "explicit"? Was soll damit verhindert bzw. bezweckt werden? Wo liegt der Sinn?

    class a_explicit
    {
    public:
        a_explicit() {}
        explicit a_explicit(int a){}
    };
    
    class a_implicit
    {
    public:
        a_implicit() {}
        a_implicit(int a){}
    };
    
    void foo_e(a_explicit const& ae)
    {
    }
    
    void foo_i(a_implicit const& ai)
    {
    }
    
    void bar()
    {
        foo_e(1); // geht nicht
        foo_i(1); // geht schon
    
        a_explicit ae;
        a_implicit ai;
        ae = 1; // geht nicht
        ai = 1; // geht schon
    }
    

    Der Sinn ist also implizite Konvertierungen zu "verbieten" wo sie sonst erlaubt wären. Und das ist meistens das was man möchte. -> einfach immer "explicit" vor jeden Ctor schreiben, ausser man möchte ganz bewusst implizite Konvertierungen erlauben (wie z.B. std::string::string(const char*) damit man String-Literale verwenden kann wo ein std::string erwartet wird).



  • Muss ich eigentlich eine implizite Initialisierung als "copy initialization" ansehen, oder kann ich das auch als "direct initialization" ansehen, weil eigentlich ist es doch so, das "(...)" == "=..." ist oder?
    int a=10;
    int a(10);
    ???
    Also:

    class A; //Konstruktor implizit
    
    int main()
    {
        A=12; //Passiert da dann dass hier --> A=A(12) --> Kopierkonstruktor
              //oder dass hier --> A(12) --> normaler Konstruktor
        return 0;
    }
    

    Oder kann man das nicht so genau sagen, da es immer irgendwie als Konstruktor weg optimiert wird? Oder wie läuft das nochmal ab?

    MfG
    Stromberg



  • Soweit ich weiß benutzt der Compiler da immer den normalen Konstruktor, außer auf der rechten Seite ist auch ein Objekt vom Typ A, dann natürlich den Copy-Konstruktor



  • Initialisierung mit "A a = 12;" ist copy initialization, wobei aber der copy-ctor "elided" (weggelassen) werden darf.
    Ist also AFAIK äquivalent zu "A a = A(12);" bzw. "A a(A(12));".



  • Wie man sich täuschen kann 🤡


Anmelden zum Antworten