Style Frage, namespace und class



  • Moin 😉

    eine Frage der Organisation, also ich habe 2 Klassen die von der Zweckbestimmung zusammengehören jedoch unabhängig voneinander sind. Also keine Erbschaftsanglegenheiten. Konkret ist das eine Klasse (TE ) die ein Template mit string-Platzhaltern rendert und eine andere Klasse (Loops) die zum expandieren von Loops in einem Template die render-Methode der TE-Klasse nutzt. Der Sinn einer Vererbung ist hier nicht gegeben. Um die Kapselung zu verbessern, könnte ich die beiden Klassen in einem dedizierten Namespace unterbringen.

    namespace Templating{
      class TE{};
      class Loop{}; 
    }
    

    Und damit zu Frage wie Ihr das machen würdet.

    Viele Grüße!



  • @_ro_ro Wenn die Klassen zusammen gehören, würde ich das über einen Namespace kenntlich machen. Insbesondere auch, wenn die Namen so schön kurz sind wie Loop kommt es sonst nacher auch zu Namenskonflikten mit 3. Bibliotheken oder eigenem anderen Code.



  • @Schlangenmensch

    done 😉

    Danke und Gruß!



  • @_ro_ro

    und noch etwas in Sachen Style. Gerade weil es immer wieder Missverständnisse gibt was den Unterschied zwischen Klassenvariablen und Instanzeigenschaften betrifft, macht es genau diese Sache verständlicher wenn man in allen seinen Methoden (auch im Konstruktor) den Zugriff auf eine Instanzeigenschaft als this->eigenschaft notiert und auch beim Aufruf der Methoden stets den Zeiger auf die Instanz notiert like this->method().

    Das ist in Perl und in PHP so und das ist gut so. Wie geht Ihr damit um?

    MFG



  • Das regelt der C++ Compiler, weil es Lookup-Prioritäten gibt. Die von dir gezeigte this-Notation braucht man meistens nicht (außer zB. bei Vererbungen von template-Klassen), und ich benutze sie auch nicht.

    Man kann Sachen wie sowas hier machen:

    void myclass:some_func()
    {
       // ohne Qualifikation: ruft die Memberfunktion auf
       some_other_func();
    
       // führende Doppelpunkte qualifizieren den global namespace, also wird die freie Funktion
      // im global namespace aufgerufen.
       ::some_other_func();
    }
    
    // Funktion im global namespace
    void some_other_func()
    {
    }
    
    // Memberfunktion
    void myclass:some_other_func()
    {
    }
    


  • @DocShoe

    danke für Dein feedback. Nun, wer std:: notiert kann auch this-> notieren 😉
    Ne, im Ernst, ich meine das alles zusammen so:

    class BaseClass{
        public: 
        std::string name;
        std::string get_name(){
            return this->name;
        };
    };
    
    class SubClass : public BaseClass{
        public:
        // Eigenschaften der Instanz sind in der Basisklasse deklariert
        // Im Konstruktor der Unterklasse werden den Eigenschaften 
        //   die Werte zugewiesen
        SubClass(const std::string &name){
            this->name = name;
        };
    };
    
    int main(){
        SubClass so("Müller");
        std::cout << so.get_name() << std::endl;
        return 0;
    }
    

    Da der Construktor ohnehin nicht geerbt wird, kann man, sofern er über die Unterklasse nicht aufgerufen wird, auf die Deklaration verzichten. Geerbt werden wie gehabt, Eigenschaften (ohne Wert) und Methoden. Unter dessen übernimmt der Konstruktor der Subklasse die Wertzuweisung an die geerbten Eigenschaften.

    Für mich klingt das alles schlüssig, es dient der Lesbarkeit und entspricht meinen Vorstellungen einer OOP.

    Viele Grüße!



  • @_ro_ro
    Würde ich nicht so machen. this braucht man an den Stellen einfach nicht. Wenn man Variablen als Member kenntlich machen will, kann man ein m_ vorstellen, oder ein _ nachstellen, aber alles mit this zu schreiben ist too much und unüblich. std:: schreibt man ja, weil es aus einem anderen Namespace kommt und man den nicht dauerhaft inkudieren möchte, damit man keine Probleme durch Namensdoppelungen bekommt.

    Initialisierung auch besser so:

    SubClass(const std::string &name):
    name(name){};
    

    Ist am Ende wieder der unterschied zwischen copy initialization und direct initialization.

    Was ich auch schon gesehen habe, um die Unterschiede klar zu machen, wäre so was:

    SubClass(const std::string &aName):
    name(aName){};
    


  • @_ro_ro sagte in Style Frage, namespace und class:

    Da der Construktor ohnehin nicht geerbt wird, kann man, sofern er über die Unterklasse nicht aufgerufen wird, auf die Deklaration verzichten. Geerbt werden wie gehabt, Eigenschaften (ohne Wert) und Methoden. Unter dessen übernimmt der Konstruktor der Subklasse die Wertzuweisung an die geerbten Eigenschaften.

    Meh, damit kann ich mich nicht anfreunden. Wenn der Konstruktor einen Parameter für das Attribut der Basisklasse hat, dann sollte der Konstruktor der Basisklasse diesen Parameter auch haben. Ich verwende mal deine Art Variablen zu benennen:

    struct Base
    {
       std::string name
       Base( std::string const& name ) : name( name )
       {
       }
    };
    
    struct Super : Base
    {
       Super( std::string const& name ) : Base( name )
       {
       }
    };
    


  • @DocShoe

    nun, in der Basisklasse sind die Attribute nur deklariert, das hat ja ersteinmal mit Vererbung nix zu tun. Wenn man diese Attribute jedoch erben will, also so daß da auch was drinsteht (!), muss man aus der Unterklasse heraus den Konstruktor der Baisklasse aufrufen und die Werte zur Initialisierung übergeben. Oder man initialisiert die Attribute eben nur in der SubClass ohne den Konstruktor der BaseClass zu bemühen.

    Dieses Verhalten ist in Perl und in PHP übrigens ganz genauso.

    Mit freundlichen Grüßen.



  • C++ ist weder Perl noch PHP. Wenn du Konzepte einer anderen Sprache 1:1 auf C++ überträgst lässt du vermutlich C++ Benefits liegen. Das ist in diesem Beispiel nicht nur eine Coding-Style Frage, sondern hat auch Auswirkungen auf die Performance.

    class Base
    {
    private:
       std::string Name_;
    public:
       Base() = default;
       Base( std::string const& name ) : Name_( name )
       {
       }
       std::string const& name() { return Name_; }
    
    //protected:?
       void set_name( std::string const& name ) { Name_ = name; }
    };
    
    class Super : public Base
    {
    public:
       // Konstruktor 1
       Super( std::string const& name ) :
          Base( name )
       {
       }
       // Konstruktor 2
       Super( std::string const& name )
       {
          set_name( name );
       }
    };
    

    Konstruktor 1 hat Konstruktor 2 gegenüber den Vorteil, dass er mit weniger Kopien auskommt, weil Name_ direkt initialisiert werden kann. Außerdem musst du immer einen Manipulator anbieten, selbst wenn Name_ Nur-Lesezugriff anbieten soll.



  • @DocShoe sagte in Style Frage, namespace und class:

    sondern hat auch Auswirkungen auf die Performance.

    Was genau hat jetzt Auswirkungen auf die Performance?

    MFG


  • Gesperrt

    @_ro_ro sagte in Style Frage, namespace und class:

    Was genau hat jetzt Auswirkungen auf die Performance?

    Die Polymorphie und dynamische Bindung.



  • @_ro_ro Ich möchte anmerken, dass keine der Klassen in deinem wie auch dem von @DocShoe gezeigten Code eine polymorphe Klasse ist. Alle Argumente auf dieser Schiene sind dadurch hinfällig, auch wenn Polymorphie in anderem Kontext durchaus (oft aber nur geringfügig) performancerelevant sein kann.

    Man sollte Versuche, diese Diskussion hier zu sabotieren besser ignorieren - auch wenn die für Einsteiger nicht immer leicht zu entlarven sind, da unser Troll seine Beiträge gerne in eine pseudo-kompetente Fassade kleidet. Ich empfehle "User blockieren", das spart eine Menge Ärger.



  • @Finnegan

    keen Problem, hab längst kapiert wo's hier lang geht 😉

    Schönen Abend!



  • Versuche doch noch einmal, den Post von @DocShoe zu verstehen.

    Vielleicht ist es wichtig zu verstehen, dass der Konstruktor zuerst alle Member-Variablen erstellt (und von allen nicht-POD-Membern den Konstruktor aufruft (in der Reihenfolge, wie sie in der Klasse deklariert sind) und erst danach den Code zwische den { ... } ausführt.

    Ein

    struct X {
        std::string s;
        X() { s = "Hallo"; }
    };
    X x;
    

    entspricht also ungefähr einem

    string x_s;  // leer initialisieren
    x_s = "Hallo"; // zuweisen
    

    Wohingegen ein

    struct Y {
        std::string s;
        Y()
          : s("Hallo")
        { };
    };
    Y y;
    

    einem

    std::string y_s("Hallo"); // direkt initialisieren
    

    entspricht, also direkt initialisiert wird und nicht erst der parameterloste Konstruktor aufgerufen wird.


  • Gesperrt

    @Finnegan sagte in Style Frage, namespace und class:

    @_ro_ro Ich möchte anmerken, dass keine der Klassen in deinem wie auch dem von @DocShoe gezeigten Code eine polymorphe Klasse ist. Alle Argumente auf dieser Schiene sind dadurch hinfällig, auch wenn Polymorphie in anderem Kontext durchaus (oft aber nur geringfügig) performancerelevant sein kann.

    Man sollte Versuche, diese Diskussion hier zu sabotieren besser ignorieren - auch wenn die für Einsteiger nicht immer leicht zu entlarven sind, da unser Troll seine Beiträge gerne in eine pseudo-kompetente Fassade kleidet. Ich empfehle "User blockieren", das spart eine Menge Ärger.

    Fragt sich nur, wer hier der größere Troll ist... Du oder @_ro_ro 😂

    Mal im Ernst, mit 70 ist man zu alt für die Softwareentwicklung.



  • @wob , danke, ja das erscheint mir schlüssig. Ich habe das schon in Perl vermieden, die Eigenschaften über den Aufruf des Konstruktors der Superklasse zu initialisieren. Viele Grüße!



  • @_ro_ro sagte in Style Frage, namespace und class:

    Ich habe das schon in Perl vermieden, die Eigenschaften über den Aufruf des Konstruktors der Superklasse zu initialisieren.

    Nur damit ich sicher bin, dass du es richtig verstanden hast: du sollst den Konstruktor der Superklasse verwenden! Aber eben mit dem Doppelpunkt direkt aufrufen. Also sowas wie Derived::Derived(const type& variable): Base(variable), derived_member_variable(42), ... {} - sonst hast du immer den Doppelschritt. Beachte außerdem, dass, anders als in Java zum Beispiel, im {...}-Bereich des Base-Konstruktors der Typ noch Base ist, auch wenn du gerade ein Derived erzeugst. Also das Aufrufen einer virtuellen Funktion im Base-Konstruktor ruft noch nicht die derived Funktion auf.



  • @wob sagte in Style Frage, namespace und class:

    Nur damit ich sicher bin, dass du es richtig verstanden hast: du sollst den Konstruktor der Superklasse verwenden!

    Mit Doppelpunkt ist klar aber warum soll ich den Konstruktor der Superklasse überhaupt aufrufen wenn ich alle in der Superklasse deklarierten Eigenschaften im Konstruktor der Subklasse initialisieren kann? Der ~Destruktor der Superklasse wird doch in jedem Fall aufgerufen egal ob es einen Konstruktor gibt oder nicht.

    Viele Grüße!



  • @_ro_ro Weil die private Member von Base in der Initialisierungsliste der Derived Klasse nicht zugreifbar sind (Kapselung) und wegen Wiederverwendbarkeit 😉

    class Base
    {
    public:
        Base(int a, int b):
        a(a),
        b(b)
        {}
    private:
        int a{};
        int b{};
    };
    
    class Derived1 : public Base
    {
     public:
        Derived1 (int a, int b, int c):
        Base(a , b),
        c(c)
        {}
    private:
       int c{};
    };
    
    class Derived2 : public Base
    {
     public:
        Derived2 (int a, int b, double c):
        Base(a , b),
        c(c)
        {}
     private:
       double c{};
    };
    

    Das geht nicht:

    class Derived1 : public Base
    {
     public:
        Derived1 (int a, int b, int c):
        c(c),
        a(a), // wir können hier auf a nicht zugreifen
        b(b) 
        {}
    private:
       int c{};
    };
    

Anmelden zum Antworten