Nicht aufgelöste Elemente bei Konstruktor Überladung



  • Hallo C++ Gemeinde,

    Ich bekomme folgende Fehlermeldung, auf die ich mir
    keinen Reim machen kann, weil für mich alles eindeutig formuliert erscheint.
    Verweis auf nicht aufgelöstes externes Symbol ""public: __thiscall Point3D::Point3D(void)" (??0Point3D@@QAE@XZ)" in Funktion "_main".

    mein .hpp-File:
    // Klasse die einen Punkt im 3 dimensionalen Raum beschreibt

    class Point3D
    {
    private:    double x, y, z;
    public:      // Konstruktor -- Default
    	Point3D( void );
    	     // Konstruktor -- Mit Parametern	      
    	Point3D(double x_, double y_, double z_);
    

    mein .cpp File:

    #pragma once
    #include "19_5_class_Point_3D.hpp"
    
    Point3D::Point3D( double x_, double y_, double z_ ) 
    {
    	x = x_; y = y_; z = z_;
    }
    

    mein main File:

    #include <iostream>
    #include <fstream>
    #include "19_5_class_Point_3D.hpp"
    
    int main ( void )
    {       // Auruf Default Konstruktor
    	Point3D p1, p2; 
            // , p4(4, 6, 8); // Auruf Konstruktor mit Para.
    	Point3D p3(0.2, 0.4, 0.6);
    

    ...Also ich weiss nicht genau warum der Compiler meckert,
    beim default-Konstruktor muss doch kein
    expliziter Body hin !?

    Viele Grüsse,
    0xFF



  • @0xFF

    beim default-Konstruktor muss doch kein
    expliziter Body hin !?

    Wenn du nicht explizit sagst, dass der Default Body generiert werden soll, schon.

    Point3D() = default;
    


  • Das ist eine Linker-Fehlermeldung, denn du hast den Default-Konstruktor zwar deklariert, aber nicht definiert.
    Aber benötigst du überhaupt einen Default-Konstruktor?
    Wäre Point3D(double x_ = 0, double y_ = 0, double z_ = 0) als alleiniger Konstruktor nicht sinnvoller?

    Außerdem solltest du die Member-Werte im Konstruktor besser mittels der Initialisierungsliste zuweisen (auch wenn der jetzige Code bei int-Werten semantisch keinen Unterschied macht).

    PS: Das `#pragma once' (Include-Guard) gehört in die Headerdatei!



  • @Th69 Danke für die Antwort,
    es ist eine Übung, aus einem Buch,
    aber Du hast recht,
    ein explizierter Konstruktor macht mehr Sinn.

    Es funktioniert mit = default



  • @0xFF sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    @Th69 Danke für die Antwort,
    es ist eine Übung, aus einem Buch,
    aber Du hast recht,
    ein explizierter Konstruktor macht mehr Sinn.

    Es funktioniert mit = default

    Es würde auch mit

    Point3D() = delete;
    

    funktionieren. Dann hättest du auch wirklich keinen Konstruktor ohne Parameter. Durch "default" hast du einen, den vermutlich gar nicht brauchst.



  • @It0101
    Hi, damit kann man also, einen Konstruktoraufruf ohne
    Paramter explizit ausschliesen ?
    Macht bei der Init. eines 3D-Punktes mehr Sinn - absolut.


  • Mod

    @0xFF sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    @It0101
    Hi, damit kann man also, einen Konstruktoraufruf ohne
    Paramter explizit ausschliesen ?
    Macht bei der Init. eines 3D-Punktes mehr Sinn - absolut.

    Ja. Eben wie von It0101 gezeigt. Wobei das die neue Art und Weise ist ("Neu" heißt dabei seit 10 Jahren...). Über die alte Art und Weise (Konstruktor ohne Argumente deklarieren, aber nicht definieren) bist du hier zufällig selber gestolpert.

    Übrigens:

    int main ( void );
    Point3D( void );
    void foo (void);
    

    Das void ist absolut unnötig. Das ist komplett identisch zum besser lesbaren

    int main ();
    Point3D();
    void foo ();
    

    Wir sind hier nicht in C, wo es tatsächlich einen kleinen Unterschied geben würde.



  • Man sollte aber noch bemerken, dass das Löschen des Konstruktors ohne Argumente auch unerwünschte Effekte haben kann. So gibt es Probleme in vector oder map, die für bestimmten Operationen in der Lage sein müssen, ein Element zu erstellen. (also z.B. muss vector::resize ja irgendwie neue Elemente erstellen und map::operator[] muss ja ebenfalls Elemente erzeugen können). Daher ist es häufig doch sinnvoll, einen zunächst unsinnig erscheinenden Konstruktor zu haben. Initialisiere die Punkte einfach mit 0,0,0. Ok, Problem tritt nicht auf, wenn dein Konstruktor für alle Parameter Default-Werte hat.


  • Mod

    @wob sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Man sollte aber noch bemerken, dass das Löschen des Konstruktors ohne Argumente auch unerwünschte Effekte haben kann. So gibt es Probleme in vector oder map, die für bestimmten Operationen in der Lage sein müssen, ein Element zu erstellen. (also z.B. muss vector::resize ja irgendwie neue Elemente erstellen und map::operator[] muss ja ebenfalls Elemente erzeugen können). Daher ist es häufig doch sinnvoll, einen zunächst unsinnig erscheinenden Konstruktor zu haben. Initialisiere die Punkte einfach mit 0,0,0. Ok, Problem tritt nicht auf, wenn dein Konstruktor für alle Parameter Default-Werte hat.

    Nein. Denn solche Aktionen können nicht sinnvoll sein, wenn es keinen sinnvollen Defaultkonstruktor gibt. Wenn du einen Defaultkonstruktor nur schreibst, weil dein Programm sonst nicht übersetzbar ist, dann hat dein Programm einen Logikfehler.



  • Das Konzept regular erfordert u.a. einen Defaultkonstruktor. Man mag darüber geteilter Meinung sein, aber die Core-Guidelines empfehlen, Typen nicht ohne Not nicht mindestens semiregular zu machen. http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-default0



  • @wob sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Man sollte aber noch bemerken, dass das Löschen des Konstruktors ohne Argumente auch unerwünschte Effekte haben kann. So gibt es Probleme in vector oder map, die für bestimmten Operationen in der Lage sein müssen, ein Element zu erstellen. (also z.B. muss vector::resize ja irgendwie neue Elemente erstellen und map::operator[] muss ja ebenfalls Elemente erzeugen können). Daher ist es häufig doch sinnvoll, einen zunächst unsinnig erscheinenden Konstruktor zu haben. Initialisiere die Punkte einfach mit 0,0,0. Ok, Problem tritt nicht auf, wenn dein Konstruktor für alle Parameter Default-Werte hat.

    Ich finde den Ansatz falsch.
    Weder bei Maps noch bei Vectoren brauchst du einen Defaultkonstruktor.

    Bei Maps verwende ich größtenteils die Inplace-Konstruktion, ebenso bei vectoren.

    Bei einer map sieht das dann so aus:

    #include <string>
    #include <unordered_map>
    #include <iostream>
    
    class MyClass
    {
    public:
        MyClass() = delete;
        MyClass( int, const std::string & ) {}
        ~MyClass() = default;
    
        MyClass( const MyClass & ) = delete;
        MyClass( MyClass && ) = delete;
        MyClass &operator=( const MyClass & ) = delete;
        MyClass &operator=( MyClass && ) = delete;
    };
    
    int main()
    {
        std::unordered_map<std::string, MyClass> Objects;
        std::string Key = "test";
        Objects.emplace( std::piecewise_construct, std::forward_as_tuple( Key ), std::forward_as_tuple( 17, "Myname" ) );
        std::cout << Objects.size() << std::endl;
    }
    

    @wob danke mir später.

    Ich finde es extrem wichtig, dass eine Klasse NUR die Funktionen, Operatoren und Konstruktoren anbietet, die auch wirklich Sinn ergeben. Daher halte ich Default-Konstruktoren für unschön. Wenn man etwas zur Erfüllung des Auftrags benötigt, sollte man es implementieren, aber definitiv nicht für die Selbstbefriedigung irgendeines Containers.



  • @SeppJ sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Nein. Denn solche Aktionen können nicht sinnvoll sein, wenn es keinen sinnvollen Defaultkonstruktor gibt. Wenn du einen Defaultkonstruktor nur schreibst, weil dein Programm sonst nicht übersetzbar ist, dann hat dein Programm einen Logikfehler.

    Kommt extrem auf die Definition von "sinnvoll" an, finde ich.

    Und ein Beispiel: alle Objekte, die ich serialisieren will, müssen einen Default-Konstruktor haben. Ob ich will oder nicht. Das erfordert die Serialisierungsbibliothek, die ich nutze, so. Auch wenn ich das eigentlich nicht haben will. Gut, jetzt könntest du daherkommen und die Bibliothek in Frage stellen und 10.000 Personentage investieren. Oder eben einfach einen schnell einen Default-Konstruktor schreiben.



  • @wob
    Natürlich kann es sein, dass ein Default-Konstruktor erforderlich ist. Vielleicht liegt es ja auch daran, dass deine Serialisierungsbibliothek eben nicht mit inplace-Konstruktion arbeitet. In dem Fall ist die Bibliothek ja nicht automatisch schlecht.

    Aber: in deinem eigenen Quellcode hast du die Verantwortung.



  • @It0101 sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Objects.emplace( std::piecewise_construct, std::forward_as_tuple( Key ), std::forward_as_tuple( 17, "Myname" ) );

    Mag jetzt ein doofes Argument sein, aber

    objects[key] = MyClass(17, "MyName");

    versteht hier jeder (*), während man bei

    Objects.emplace( std::piecewise_construct, std::forward_as_tuple( Key ), std::forward_as_tuple( 17, "Myname" ) );

    C++-Experte sein muss. Und ja, wenn du mit C++ arbeitest, aber hauptsächlich mit Personen arbeitest, die keine Programmierexperten sind, dann macht das einen wesentlichen Unterschied. Und auch sonst. Guck dir die 2 Zeilen an und sag mir, wie lange du jeweils brauchst, um zu verstehen, was passiert. (abgeshen vom Unterschied, wenn der Key schon existiert) Gut, bei (*) wäre nicht jedem klar, was genau bei operator[] abläuft, aber dass im Endeffekt beim "key" das neue Objekt steht, das ist jedem klar.

    Daher haben wir auch immer mehr Python und weniger C++ :-), auch wenn man da mit selbstgemachten Loops schon die Langsamkeit (und manchmal den RAM-Bedarf) neu erfinden kann (und nicht jeder ist Spezialist in Pandas/Numpy). Will sagen: keinen Default-Konstruktor zu haben, kann zu erheblicher Verschlechterung der Lesbarkeit führen. Dazu kommt, dass ich eigentlich immer, wenn ich es brauchte, irgendwie einen Default-Konstruktor hinbekommen habe. Und zur Not kann man ja auch (Smart-)Pointer in den vector/die map tun.



  • @wob sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Will sagen: keinen Default-Konstruktor zu haben, kann zu erheblicher Verschlechterung der Lesbarkeit führen.

    Im Gegenteil. Wenn man direkt sieht "MyClass() = delete", weiß man sofort Bescheid.
    Und wenn dann auch noch die Operatoren und Copy-Konstruktoren weg sind, ist alles klar.



  • @wob sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Und zur Not kann man ja auch (Smart-)Pointer in den vector/die map tun.

    Aber dann hast du wieder unnötig Pointer verwendet, wenn auch indirekt. Ich habe das auch eine zeitlang gemacht, aus Faulheit. Aber inzwischen entstehen die meisten Objekte bei mir direkt über die Inplace-Konstruktion und Smartpointer nutze ich dann wirklich nur, wenn sie nötig sind.



  • @It0101 sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Im Gegenteil.

    Nur zur Sicherheit, weil deine Erklärung nichts mit meinem Beispiel zu tun hat: du findest also
    Objects.emplace( std::piecewise_construct, std::forward_as_tuple( Key ), std::forward_as_tuple( 17, "Myname" ) );
    einfacher zu lesen als
    objects[key] = MyClass(17, "MyName");
    ?


  • Mod

    @Bashar sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Das Konzept regular erfordert u.a. einen Defaultkonstruktor. Man mag darüber geteilter Meinung sein, aber die Core-Guidelines empfehlen, Typen nicht ohne Not nicht mindestens semiregular zu machen. http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-default0

    Das drückt aber eher aus, dass dann doch recht viele Klassen einen natürlichen Defaultstate haben. Zumindest mehr, als man oft denkt. Hier, bei einem 3D-Punkt, wäre (0,0,0) ja tatsächlich eine vernünftige Wahl, denn die Initialisierung ohne Argumente soll ja so etwas wie "mit Null initialisiert" ausdrücken.

    Die Guidelines gehen ja auch näher darauf ein: Klassen, die irgendwie "wertartig" sind, haben oft einen natürlichen Nullstatus, Klassen die "funktionalitätsartig" sind, eher nicht.



  • @wob sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    @It0101 sagte in Nicht aufgelöste Elemente bei Konstruktor Überladung:

    Im Gegenteil.

    Nur zur Sicherheit, weil deine Erklärung nichts mit meinem Beispiel zu tun hat: du findest also
    Objects.emplace( std::piecewise_construct, std::forward_as_tuple( Key ), std::forward_as_tuple( 17, "Myname" ) );
    einfacher zu lesen als
    objects[key] = MyClass(17, "MyName");
    ?

    Nein.
    Ich meinte, dass "MyClass() = delete" absolut eindeutig ist.

    Zu deinem Argument:
    Das emplace-Konstrukt ist natürlich relativ lang, aber wenn man das zwei-, dreimal verwendet hat, sieht das auch nicht mehr so gefährlich aus.


Anmelden zum Antworten