Macht sowas Sinn? (Konstruktor)



  • Tachyon schrieb:

    ..Du solltest Dir aber vielleicht abgewöhnen, die Member-Variablen über die Initialisierungsliste zu initialisieren...

    Schon gemein, dass 'n' und 'b' so dicht beieinander liegen, oder ?
    😉

    Gruß,

    Simon2.



  • eine frage bitte schrieb:

    Da hab ich folgendes probiert:

    Das ganze hat keinen Sinn, denn Defaultwerte sorgen für unnötige Doppeldeutigkeiten, die man in diesem Fall leicht umgehen kann.
    Beispiel

    #include <string>
    
    using namespace std;
    
    class Artikel {
    	private:
    		string	name;
    		long	  nummer;
    		double	preis;
    
    	public:
    		Artikel(const string& k_name = "Kein Eintrag", long k_nummer = 0, double k_preis = 0.0){
    			name   = k_name;
    			nummer = k_nummer;
    			preis  = k_preis;
    		}
    };
    
    int main () {
        Artikel hurz ("hurz");       // Artikel ohne Artikelnummer
        Artikel murks ("murks", -1); // Artikel ohne Preis
    }
    

    In beiden Fällen wird ein ein zum Default konstruiertem Objekt abweichendes Objekt erzeugt, welches aber keinen gültigen Satz Daten hat. Was bringt das?

    class Artikel {
        std::string name_;
        long nummer_;
        double preis_;
    public:
        Artikel () : name_("Kein Eintrag"), nummer_ (0), preis_ (0.0) {}
        Artikel (std::string const& name, int const nummer, double const preis)
            : name_ (name), nummer_ (nummer), preis_ (preis)
        {}
    };
    

    In diesem Fall mußt Du entweder alle drei Parameter zur Verfügung stellen, oder gar keinen.



  • eine frage bitte schrieb:

    Warum; was macht es für einen Unterschied?

    Bei elementaren Typen eigentlich keinen.

    Grundsätzlich kann man sich merken: Was in der Initialisierungsliste steht, wird gleich mit dem Objekt initialisiert. Wenn man beispielsweise einen std::string als Member hat und den erst im Konstruktorrumpf zuweist, findet zuerst eine Defaultkonstruktion statt, und nachher eine Zuweisung. Wenn man ihn hingegen in die Initialisierungsliste schreibt, hat man gleich den spezifischen Konstruktoraufruf des Members, und braucht nachher nicht mehr zuzuweisen.

    Einige Member erfordern sowieso eine Initialisierung in der Initialisierungsliste, weil sie nicht zugewiesen werden können ( const -Member, Referenzen).

    eine frage bitte schrieb:

    Nun ja, jetzt würde ich gerne wissen, ob soetwas in der Praxis auch einen Sinn macht, oder es vorteilhafter wäre, Paar Konstruktoren zu überladen(je nachdem, was man will) und nebenbei einen richtigen Default-Konstruktor anzulegen.

    Du solltest generell bei Funktionsüberladungen darauf achten, dass jeder mögliche Aufruf einen Sinn ergibt. Das heisst, wenn man 4 Standardparameter hat, soll es nicht nur für 0 und 4 explizite Argumente Sinn ergeben, sondern auch für 1, 2 und 3.

    Ansonsten, wie in deinem Fall, lohnt es sich mehr, zwei Konstruktoren bereitzustellen.

    ~john schrieb:

    Artikel (std::string const& name, int const nummer, double const preis)
            : name_ (name), nummer_ (nummer), preis_ (preis)
    

    Von dem int statt long einmal abgesehen, wieso machst du die elementaren Parametertypen konstant?



  • Nexus schrieb:

    ~john schrieb:

    Artikel (std::string const& name, int const nummer, double const preis)
            : name_ (name), nummer_ (nummer), preis_ (preis)
    

    Von dem int statt long einmal abgesehen, wieso machst du die elementaren Parametertypen konstant?

    … , weil diese Variablen im Konstruktor nicht verändert werden sollen. Man kann es auch weglassen, aber die Macht der Gewohnheit. Nicht Referenzparameter mache ich meisten "const", weil man so dem Problem aus dem Weg geht, daß man aus Versehen ihnen was zuweist, was sich dann nicht auswirkt, da es ja Kopien sind.



  • Okay. Nun ja, ich mach sie meistens nicht const , da man so Platz spart und man gleich schneller sieht, ob es sich um eine Const-Referenz handelt (das Zeichen & muss man zuerst suchen, um eine Referenz zu erkennen - je länger der Typbezeichner, desto mühsamer). 😉

    Aber ja, ist halt Geschmackssache. Mir ist kein Fall bekannt, bei dem ich eine Kopie verändert und gemeint habe, das Original werde manipuliert. Schon gar nicht bei einem Konstruktor... 🙂



  • In diesem Fall mußt Du entweder alle drei Parameter zur Verfügung stellen, oder gar keinen.

    Das ist aber eine unnötige Belästigung und du musst auch den Konstruktor 2 mal schreiben. Und im Falle einer Änderung der Klasse das für alle Konstruktoren anpassen. Das ist doch unnötig umständlich, wenn man das einfach per Defaultwerte regeln kann. (man muss halt auf die Reihenfolge achten.)



  • drakon schrieb:

    Das ist doch unnötig umständlich, wenn man das einfach per Defaultwerte regeln kann.

    Da implementiere ich lieber zwei Mal den Konstruktor als dass nachher noch die Möglichkeit besteht, ein halbwegs gültiges Objekt zu erstellen, wenn nur einige der Argumente angegeben werden. Ein Artikel mit Namen und Nummer, aber ohne Preis macht nun mal nicht viel Sinn.

    Ein Artikel ohne irgendwas im Übrigen auch nicht, hier wäre es vielleicht sogar am besten, keinen Standardkonstruktor zur Verfügung zu stellen (das kann einen halt ein wenig einschränken, aber es sollte schon gehen). So kann man sicherstellen, dass jede Artikel -Instanz wirklich einen Artikel repräsentiert.



  • Ich glaube, es ging im ganz grundsätzlich darum, ob sowas geht und ob das Sinn macht. Wie man im ersten Post des TOs lesen kann, handelt es sich hier um etwas aus einem C++-Buch bzw. um eine Übung.
    Ob dieses für das konkrete Beispiel aus Designsicht sinnvoll ist, sei mal dahingestellt.
    Grundsätzlich ist das Zuweisen von Defaultwerten jedoch ein durchaus sinnvolles Feature, und Konstruktoren die mit Defaultwerten für jeden Parameter belegt sind, geben valide Defaultkonstruktoren.



  • Nexus schrieb:

    drakon schrieb:

    Das ist doch unnötig umständlich, wenn man das einfach per Defaultwerte regeln kann.

    Da implementiere ich lieber zwei Mal den Konstruktor als dass nachher noch die Möglichkeit besteht, ein halbwegs gültiges Objekt zu erstellen, wenn nur einige der Argumente angegeben werden. Ein Artikel mit Namen und Nummer, aber ohne Preis macht nun mal nicht viel Sinn.

    Ja. Hier macht es tatsächlich nicht viel Sinn, kann es aber an anderen Orten.
    Z.B eine Kasse. Eine Kasste hat vlt. einen Namen, Maximalbetrag und einen Startbetrag (und ev. weitere Werte). Ich habe da keine Lust jedwede Kombination einen Konstruktor zu schreiben. (Vor allem, wenn es 4, oder noch mehr Werte sind).
    Das ganze hat aber rein gar nichts zu tun, ob das Objekt gültig ist, oder nicht. Die Invarianz ist ebenso gegeben, wie, wenn du per Überladung machst.
    Ob es logisch Sinn macht ist dann eine ganz andere Frage.
    Im übrigen kannst du die Parameter auch erzwingen, wenn du willst, indem du die Reihenfolge änderst..



  • drakon schrieb:

    In diesem Fall mußt Du entweder alle drei Parameter zur Verfügung stellen, oder gar keinen.

    Das ist aber eine unnötige Belästigung und du musst auch den Konstruktor 2 mal schreiben.

    Default Werte sind schlecht, aber das schrieb ich bereits.
    Beispiel warum das so ist. Faulheit war schon immer der Weg zu schlechtem Code.

    #include <string>
    
    using namespace std;
    
    class Artikel {
        string    name;
        long      nummer;
        double    preis;
    
    public:
        Artikel(string const& k_name = "Kein Eintrag", long k_nummer = 0, double k_preis = 0.0) {
            name   = k_name;
            nummer = k_nummer;
            preis  = k_preis;
        }
    };
    
    int main () {
        string s = "Defaultwerte sind schlecht!";
    
        Artikel a1;
        Artikel a2 = s; // Konversion!
    
        a1 = s; // Konversion!
    }
    


  • ~john schrieb:

    Beispiel warum das so ist. Faulheit war schon immer der Weg zu schlechtem Code.

    dann wird der Konstruktor halt explizit und der Zuweisungsoperator implementiert

    im vorliegenden Fall wuerde ich aber die Artikelbezeichnung als verpflichtenden Parameter definieren... und die Artikelnummer je nach Anforderung auch... der Preis kann ruhig optional sein



  • Tachyon schrieb:

    Grundsätzlich ist das Zuweisen von Defaultwerten jedoch ein durchaus sinnvolles Feature, und Konstruktoren die mit Defaultwerten für jeden Parameter belegt sind, geben valide Defaultkonstruktoren.

    Das ist der beste Weg sich selbst ein Bei zu stellen. Wenn man das überhaupt macht, dann bitte schon als "explicit" Konstruktor. Andernfalls hat man sich ganz schnell einen Konversionskonstruktor geschaffen, den man möglicherweise gar nicht will.



  • zwutz schrieb:

    dann wird der Konstruktor halt explizit und der Zuweisungsoperator implementiert

    Der Zuweisungsoperator ist überflüssig, er verhindert eine Konversion nicht.



  • ~john schrieb:

    Tachyon schrieb:

    Grundsätzlich ist das Zuweisen von Defaultwerten jedoch ein durchaus sinnvolles Feature, und Konstruktoren die mit Defaultwerten für jeden Parameter belegt sind, geben valide Defaultkonstruktoren.

    Das ist der beste Weg sich selbst ein Bei zu stellen. Wenn man das überhaupt macht, dann bitte schon als "explicit" Konstruktor. Andernfalls hat man sich ganz schnell einen Konversionskonstruktor geschaffen, den man möglicherweise gar nicht will.

    Nichts für ungut, aber das ist Bullshit
    Wenn man mehrere Ctors überlädt, dann hat man ebenfalls irgendwelche mehr oder weniger sinnvollen Defaultwerte im Objekt drin. Ob die nun über Defaultparameter direkt kommen, oder von zig unterschiedlichen Ctors erzeugt werden, ist völlig irrelevant.
    Außerdem sagte ich bereits, dass man sich, um den Sinn oder Unsinn von Defaultwerten bewerten zu können, vielleicht nicht unbedingt an diesem Beispiel aufhängen sollte.

    PS: Schau Dir mal die Ctors aus der STL an. Da wirst Du viele Beispiele für sinnvolle Defaultparameter finden.



  • Beispiel warum das so ist. Faulheit war schon immer der Weg zu schlechtem Code.

    Faulheit ja. Aber man muss sich ja auch nicht die unnötige Mühe machen und doppelten Code schreiben.
    Oder verzichtest du auch auf Templates und schreibst dir jede mögliche Klasse selbst?
    Ein (unnötiger) doppelter Konstruktor ist imo fehleranfälliger und gefährlicher, da sich der allfällig vergessene explicite Konstruktor nur in "fehlerhaften" Anwendungscode auswirkt, wobei ein falsch implementierter Konstruktor in Anwendungscode nicht gefunden werden kann.

    Was der Zuweisungsoperator jetzt hier zu suchen hat, entzieht sich meiner Vorstellung.



  • Ob man nur den einen Konstruktor mit Default-Parametern nutzt oder zwei (parameterloser Konstruktor und volle Initialisierung), sollte imho eher vom Anwendungsfall abhängig gemacht werden. Mit dem Default-Parameter-Konstruktor handelt man sich de facto vier Konstruktoren ein, wenn jeder davon sinnvoll ist, haben wir doch unseren Kandidaten.
    Es heißt doch, Klassen sollen nur schwer "falsch" zu benutzen sein. Wenn das hier

    Artikel[] artikel = { Artikel("Apfel"), Artikel("Birne") };
    

    im Sinne der Anwendung ok ist, ist's super. Aber bitte nicht den Konstruktor mit Default-Parametern anbieten und in der Doku sowas schreiben: "Hier entweder keinen oder alle Parameter angeben!".


Anmelden zum Antworten