Implizite Konstruktoraufrufe



  • Hi zusammen

    ich bin gerade über etwas entsetzliches gestolpert, von dem ich nicht wusste, dass es überhaupt kompiliert.

    Folgende Klasse

    class Liste
    {
    ....
    };
    

    Signatur der aufgerufenen Funktion:

    bool updateList( const Liste &liste_ )
    

    Funktionsaufruf:

    updateList( 17 );
    

    Des Rätsels Lösung: folgender Konstruktor wird benutzt:

    Liste( unsigned id_ = 0, const std::string &name_ = "", const std::string &owner_ = "", const std::string &kst_ = "", bool userdef_ = false );
    

    Wie kommt es zu diesem impliziten Konstruktoraufruf? Ist das legit Compiler-Behaviour?

    Und vor allem: hier wird ja mal eben schnell eine Instanz der Klasse "Liste" erzeugt, erwartet wird aber eine const-ref auf ein bereits existierendes Objekt.... Ich bin entsetzt.



  • Ja sicher ist das korrekt.
    Da du den ctor nicht explicit machst dient der sowohl als Default-Ctor als auch als Conversion-Ctor.

    Most underused C++ keyword: https://en.cppreference.com/w/cpp/language/explicit



  • @It0101 sagte in Implizite Konstruktoraufrufe:

    Und vor allem: hier wird ja mal eben schnell eine Instanz der Klasse "Liste" erzeugt, erwartet wird aber eine const-ref auf ein bereits existierendes Objekt.... Ich bin entsetzt.

    Auch das ist in C++ explizit erlaubt. Wenn du das verhindern willst musst du ne mutable ref oder ne const ref-ref nehmen.



  • Witzigerweise verwende ich fast überall explicit... aber genau da irgendwie nicht.

    Ich finde es halt bei der const-Referenz kreuzgefährlich da ein temporäres Objekt zu erzeugen und darauf zu referenzieren....


  • Mod

    Ja!?

    Das wird immer gemacht, wenn der Typ um eine Konvertierung entfernt ist. Wäre ganz schön unpraktisch, wenn nicht. Denk mal an die vielen Stringfunktionen, die du ansonsten nicht mit einem Literal als Argument aufrufen könntest.

    Du kannst das aber verhindern, indem du den Konstruktor explicit machst. Das ist generell eine sehr gute Praxis, Konstruktoren immer explicit zu machen, außer man hat halt eine sehr enge Bindung zwischen Typen wie man es bei char* und std::string hat, wo das eine halt nur eine andere Darstellung der gleichen Semantik ist. Wird aber leider viel zu oft vergessen.

    edit: Meine Antwort bezieht sich direkt auf die Eingangsfrage, nicht auf die Zwischendiskussion, die sich schneller ergab, als ich meine Antwort schreiben konnte.



  • @It0101 sagte in Implizite Konstruktoraufrufe:

    Ich finde es halt bei der const-Referenz kreuzgefährlich da ein temporäres Objekt zu erzeugen und darauf zu referenzieren....

    Wieso?
    Wenn du wo was by-ref übergibst was reference semantics hast, dann gehörst du eh gehauen 😉
    Und wenn es value-semantics hat, wo ist dann das Problem? Also ausser dass du explicit vergessen hast und natürlich ausser dass C++ hier mit "implicit" das falsche Default hat.



  • Wobei ich "explicit" eigentlich immer nur verwendet habe, um die Typsicherheit in den Parametern des Konstruktors zu erhöhen. Aber es verhindert wohl auch das implizite aufrufen von Konstruktoren...

    @SeppJ: stimmt, mit den String-Literalen hast du recht. Das habe ich gar nicht so bedacht.

    Meine Nachlässigkeit. In Zukunft werde ich darauf achten, stets explicit einzusetzen. Geplant ist ja, explicit auch für alle Arten von Funktionen einzuführen. C++20 ?


  • Mod

    @It0101 sagte in Implizite Konstruktoraufrufe:

    Geplant ist ja, explicit auch für alle Arten von Funktionen einzuführen. C++20 ?

    Das hast du wohl falsch verstanden. Funktionsaufrufe abseits der impliziten Konvertierungen sind ja sowieso schon immer explicit. Was in C++20 neu ist, ist dass man bestimmte Überladungen/Spezialisierungen der ansonsten impliziten Konvertierungfunktionen konditionell explicit machen kann. Das ist nützlich bei Templatemagie, und der Sinn dahinter ist eigentlich nur, dass man es als kurze Abkürzung benutzen kann, für das was heutzutage mehrere Zeilen von enable_ifs und SFINAEs wären.

    So in der Art von:

    struct Foo
    {
      template<typename T> explicit(/* hier irgendwelche Magic, die entschiedet ob für T explicit oder nicht*/) 
          Foo(const T&) {...}
    }
    


  • @It0101 sagte in Implizite Konstruktoraufrufe:

    Wobei ich "explicit" eigentlich immer nur verwendet habe, um die Typsicherheit in den Parametern des Konstruktors zu erhöhen.

    Will sehen. Denn...

    Aber es verhindert wohl auch das implizite aufrufen von Konstruktoren...

    Nein, es verhindert nur das. Ein Ctor den du explicit markierst kann nicht für implizite Konvertierungen verwendet werden. Aber sehrwohl mit impliziten Konvertierungen (also die an diesen Ctor übergebenen Parameter können sehr wohl über implizite Konvertierungen entstehen).

    Also

    class Foo {
    public:
        explicit Foo(std::string const& s);
    };
    
    void takeFoo(Foo const& f);
    
    void test() {
        takeFoo(Foo{ "hui" }); // OK
    
        std::string s = "pfui";
        takeFoo(s); // fehler weil Foo::Foo explicit ist
    }
    

    Geplant ist ja, explicit auch für alle Arten von Funktionen einzuführen. C++20 ?

    Hast du nen Link dazu? Glaub ich nämlich ebenso nicht 😉



  • ok da bin ich wohl einem großen Irrglauben bzgl. "explicit" aufgesessen. Danke für die Aufklärung 😉


Anmelden zum Antworten