Membervariable über Memberfunktion initialisieren


  • Gesperrt

    Hallo,

    stosse beim Programmieren immer wieder auf die gleiche Frage. Wenn eine Memberfunktion eine Membervariable initialisieren (schreibt man eigentlich immer initialisieren, wenn man einer Variable einen Wert zuweist?) soll, habe ich in der Funktion eine lokale Variable, bei der ich mich immer frage, wie ich sie nennen soll.

        #include <iostream>
         
        class A
        {
        public:
        	A(size_t size) : size(size) {}
        	void Set0(size_t size);
        	void Set1(size_t size);
        	void Get();
        private:
        	size_t size;
        };
         
        void A::Set0(size_t size)
        {
        	size = size;	
        }
         
        void A::Set1(size_t size)
        {
        	this->size = size;
        }
         
        void A::Get()
        {
        	std::cout << size << '\n';
        }
         
        int main(int argc, char** argv)
        {
        	A aobject(0);
        	aobject.Get();
         
        	aobject.Set0(1);
        	aobject.Get();
         
        	aobject.Set1(2);
        	aobject.Get();
         
        	return 0;
        }
    

    Ausgabe

    0
    0
    2
    

    Wenn die lokale Variable des Funktionsparameters den gleichen Namen hat, wie die zu initalisierende Membervariable, klappt es bei aobject.Set0(1);nicht. Wie soll ich den Funktionsparameter umbenennen? Oder ist es besser mit dem this-Zeiger die Membervariable in die Memberfunktion zu "holen"?



  • @titan99_ sagte in Membervariable über Memberfunktion initialisieren:

    Wie soll ich den Funktionsparameter umbenennen? Oder ist es besser mit dem this-Zeiger die Membervariable in die Memberfunktion zu "holen"?

    Das mit dem this->size=size; finde ich gut. Ist am besten lesbar und wie der Funktionsparameter heißt spielt dann auch keine Rolle mehr. Bei size=size; sollte es eine Warnung geben wie "Anweisung ohne Wirkung".



  • initialisieren kommt von init = anfänglich. Also wenn die Variable bei der Erzeugung einen Anfangswert bekommt, heißt es Initialisierung. Gibt es keinen Afangswert, heißt es Definition. Wird nichts erzeugt, heißt es Deklaration.

    Das meist verbreitete dürfte wohl der Suffix Underscore sein:

    #include <cstddef>
    
    class A
    {
    public:
        A( std::size_t size ) : size_( size )
        {
        }
        void resize( std::size_t size )
        {
            size_ = size;
        }
        
    private:
        std::size_t size_;
    };
    
    int main()
    {
        A object{ 0 };      // Nutze {} zur Initialisierung für benutzerdefinierte Datentypen
        object.resize( 1 ); // Der Datentyp einer Variablen hat im Variablennamen nichts verloren, den
        // seh ich an der Initialisierung.
        
        // return 0; // return 0 wird in der main automatisch gemacht.
    }
    
    PS: Wenn du glaubst, für jede Membervarialbe setter und getter zu brauchen, machst du etwas falsch.
    

  • Gesperrt

    Vielen Dank für die Antworten 😀

    @out sagte in Membervariable über Memberfunktion initialisieren:

    Das meist verbreitete dürfte wohl der Suffix Underscore sein:

    Kann glaube ich auch postfix genannt werden (oder prefix wenn es an den Anfang kommt, deutschsprachig Vorsilbe oder Nachsilbe). Ich finde der Sinn ist dann auch nicht so klar, man kann dann gerade so gut z.B. set_size als Variablennamen nehmen.

    Habe noch mit const versucht. Aber der Compiler gibt eine Fehlermeldung aus, wegen Zuordnung auf const.

    void A::Set2(const size_t size)
    {
    	size = size;
    }
    

    @out sagte in Membervariable über Memberfunktion initialisieren:

    Nutze {} zur Initialisierung für benutzerdefinierte Datentypen

    Weshalb nicht normale Klammern? Oder was ist der Unterschied zu normalen Klammern?

    return 0 wird in der main automatisch gemacht.

    War mir neu. Vielen Dank. Ist aber nicht so etwas ähnliches wie void main()?

    Wenn du glaubst, für jede Membervarialbe setter und getter zu brauchen, machst du etwas falsch.

    Weiss ich bereits, ist im konkreten Fall glaube ich nötig, habe aber Beispiel vereinfacht.

    Also wenn die Variable bei der Erzeugung einen Anfangswert bekommt, heißt es Initialisierung.

    Ja, aber was wenn die Variable definiert ist und erst später ein Wert zugewiesen wird? Oder wenn ein neuer Wert zugewiesen wird?



  • @titan99_ sagte in Membervariable über Memberfunktion initialisieren:

    @out sagte in Membervariable über Memberfunktion initialisieren:

    Nutze {} zur Initialisierung für benutzerdefinierte Datentypen

    Weshalb nicht normale Klammern? Oder was ist der Unterschied zu normalen Klammern?

    liest Du Most Vexing Parse.

    @titan99_ sagte in Membervariable über Memberfunktion initialisieren:

    @out sagte in Membervariable über Memberfunktion initialisieren:

    return 0 wird in der main automatisch gemacht.

    War mir neu. Vielen Dank. Ist aber nicht so etwas ähnliches wie void main()?

    Nein. Die vom Standard geforderten Versionen von main() (int main() und int main(int argc, char **argv)) geben 0 zurück, wenn sie kein return-Statement enthalten.

    @out sagte in Membervariable über Memberfunktion initialisieren:

    Also wenn die Variable bei der Erzeugung einen Anfangswert bekommt, heißt es Initialisierung.

    Ja, aber was wenn die Variable definiert ist und erst später ein Wert zugewiesen wird? Oder wenn ein neuer Wert zugewiesen wird?

    Dann heißt das Zuweisung. Assignment. Siehe assignment-operator.



  • @titan99_ sagte in Membervariable über Memberfunktion initialisieren:

    Wenn die lokale Variable des Funktionsparameters den gleichen Namen hat, wie die zu initalisierende Membervariable, klappt es bei aobject.Set0(1);nicht. Wie soll ich den Funktionsparameter umbenennen? Oder ist es besser mit dem this-Zeiger die Membervariable in die Memberfunktion zu "holen"?

    Ich persönlich verwende den Prefix m_ für Membervariablen. Dadurch heisst die Membervariable dann m_size und es gibt keine Kollision mehr. Finde ich besser als überall this->size zu schreiben.

    Und normalerweise einfach nur size zu schreiben, aber dann selektiv auf this->size auszuweichen wo es nötig ist, finde ich am schlimmsten (weil verwirrend).


  • Gesperrt

    @hustbaer sagte in Membervariable über Memberfunktion initialisieren:

    Und normalerweise einfach nur size zu schreiben, aber dann selektiv auf this->size auszuweichen wo es nötig ist, finde ich am schlimmsten (weil verwirrend).

    Die "Selektion" ist ja dann darauf beschränkt, wenn einer Membervariable ein Wert direkt über einen Funktionsparameter und ausserhalb des Konstruktors zugewiesen wird (Oder beim Konstruktor ausserhalb der initializer list) .

    Danke auch für die Erklärung von Deklaration, Definition, Initialisation und Zuweisung. 🤔



  • Ich nenne so einen Parameter nicht wie den Member, maximal ähnlich. Erstmal muss ich im öffentlichen Interface nicht verraten, wie meine privaten Variablen heissen. Zweitens wird der Wert vlt. gar nicht 1:1 übernommen, damit wäre ja auch jegliche Kapselung ausgehebelt. Auf so ein size würde ja in vielen Fällen erstmal ein clamp gemacht werden.


  • Gesperrt

    @tggc

    void CircleShape::SetResolution(size_t resolution)
    {
    	this->resolution = resolution;
    	ResizeBuffer(resolution * 3);
    }
    

    Es handelt sich im konkreten Fall um eine "Hilfsvariable":

    for (auto index = 0U; index < resolution; ++index) {}
    
    float step = 2.f * glm::pi<float>() / static_cast<float>(resolution); 
    

    sonst müsste jedesmal durch 3 teilen:

    for (auto index = 0U; index < vertices.size() / 3U; ++index) {}
    
    float step = 2.f * glm::pi<float>() / static_cast<float>(vertices.size() / 3U);
    


  • D.h. du gibst mir recht, weil dein Code crashed?


  • Gesperrt

    Weil es kein clamp hat? Also die untere Grenze wäre dann 3. Aber würde eher eine Fehlermeldung ausgeben lassen, als dass klammheimlich der Wert geändert wird. Zudem ist es eine abgeleitete Klasse und in der jetzigen Phase halte ich es für vorteilhaft, alle Eingaben zuzulassen. Auch kenne mich mit exceptions und asserts usw. auch nicht gut aus. Und wenn, würde ich nicht bei abgeleiteten Klassen anfangen diese einzufügen. Hinzu kommt, dass eine "Vertex-Pumpe" involviert ist, die möglichst effizient und einfach sein soll.

    Edit: Die untere Grenze wäre bei den vertices 3, bei der resolution 1.



  • Na gut. Dann frag halt nicht, wenn du nur deine Meinung bestätigt haben möchtest selbst wenn du Crashes und undefinierstes Verhalten programmierst...


  • Gesperrt

    Also eigentlich hoffte ich auf ein brainstorming, was ja geschah.

    • Habe prefix bzw. suffix, postfix als Antworten bekommen, darunter z.B. das prefix m_ bei privaten(?) Membervariablen.
    • Zusätzlich Deklaration, Definition, Initialisation und Zuweisung bei Variablen.
    • Most Vexing Parse wegen den Klammern im Zusammenhang mit dem Konstruktor.
    • int main() braucht nicht unbedingt ein return statement, wenn 0 zurückgegeben werden soll und dies hat nicht mit void main() zu tun, weil es angeblich im Standard so gefordert wird.
    • Verschiedene Namensfindungen für Variablen und entsprechende Gründe.

    Auf dieser Grundlage kann ich dann entscheiden, wie ich die Membervariable und den Funktionsparameter nennen kann. Gut möglich, dass undefiniertes Verhalten geschieht oder es näher am undefinierten Verhalten ist. Aber falls der Code bei manchen Eingaben crashed, möchte ich wissen, wo der Code crashed. Wenn ich den crash mal hier mal dort durch Eingabebeschränkungen in abgeleiteten Klassen verhindere, finde ich eventuell den Grund in der Basisklasse nicht mehr.
    Es ist vielleicht ein hinkender Vergleich, aber ein Bildhauer fängt auch nicht mit den Feinarbeiten an. Würde er das, müsste er bei groben Änderungen die Feinarbeiten wiederholen und hätte so mehr Arbeit.

    Es passt zwar nicht mehr zum Thementitel, aber wenn ich die Memberfunktion SetResolution privat "mache"(?), ergeben sich ohne weitere Umwege bereits Einschränkungen die crashes verhindern. Behalte statische und dynamische CircleShapes in einer Klasse, ohne das beim statischen CircleShape der Buffer bei jeder Draw-Anweisung neu geladen werden muss. Bei der dynamischen Draw-Anweisung des CircleShape kann aber die resolution geändert werden und muss nicht fix bei 64 bleiben, wie derzeit im Konstruktor zugewiesen.

    So habe ich auf einfache Weise dynamische und statische CircleShape in einer Klasse. Möchte ich in der Basisklasse zum OpenGL Interface etwas ändern, muss ich höchstens sehr wenig in den abgeleiteten Klassen ändern. Möchte ich einen geometry-shader irgendwann implementieren oder den Buffer als Pointer holen, so kann ich irgendwann bei allen vier oder mehr Implementierungen die Performance vergleichen, was ich aber im Moment nicht wichtig finden.

    Sollte der Code wegen identischen Variablennamen der Membervariable und des Funktionsparameters crashen, so kann ich jederzeit der Membervariable ein m_ prefix voranstellen. Zudem kapsle ich auch nicht alles, ein Teil der Basisklasse ist protected.

    @tggc sagte in Membervariable über Memberfunktion initialisieren:

    Zweitens wird der Wert vlt. gar nicht 1:1 übernommen, damit wäre ja auch jegliche Kapselung ausgehebelt.

    Wieso wäre dann die Kapselung ausgehebelt?



  • @tggc sagte in Membervariable über Memberfunktion initialisieren:

    Zweitens wird der Wert vlt. gar nicht 1:1 übernommen, damit wäre ja auch jegliche Kapselung ausgehebelt.

    Wieso wäre dann die Kapselung ausgehebelt?

    Wenn du eine private Variable x hast, der man dank der Set Funktion jeden Wert zuweisen kann, dann ist die nur noch auf dem Papier private. Du müsstest dann bei jeder Verwendung von x testen, ob da nicht Unsinn drin steht und falls doch hast du keine Ahnung warum. Daher ist auch der Vergleich mit einem Bildhauer an der Stelle IMHO nicht sinnvoll. Objektorientierte Programmierung ist extra dafür ausgelegt, das du deine Klasse mit dem Member x nur von "innen" checken musst, also sich alle Fehler die in x Unsinn reinschreiben innerhalb der Klasse befinden und nicht sonstwo im Code. Wenn man diesen Grundsatz erst mal verstanden hat, wird einem auch klar, das es eine wichtigen Unterschied von Member x und Parameter x gibt, der weit über den Namen hinausgeht.


  • Gesperrt

    @tggc

    Danke für die Erklärung (Entschuldigung für meine schlechte Laune heute morgen 😣 ).

    Also SetResolution ruft auch ResizeBuffer der Basisklasse auf. Dort wird dann mit glBufferData(GL_ARRAY_BUFFER, buffersize, nullptr, GL_STREAM_DRAW) die Buffergrösse festgelegt, soweit ich weiss aber auch der Inhalt "gelöscht" bzw. "überschrieben" (Deshalb übergebe ich einen nullptr und keinen pointer auf die vertices), da nur die Buffergrösse festgelegt werden soll.

    Mit glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(VertexVN), vertices.data())"lade" ich dann in der Memberfunktion SetBuffer der Basisklasse die vertices in den Buffer.

    Es kann dann sein, wenn ich nach SetShape(position, radius) SetResolution aufrufe (in SetShape wird auch SetBuffer aufgerufen) , die vertices nicht mehr im Buffer sind und die Draw Anweisung "crasht".

    SetResolution war public, dann private jetzt protected, aber in einer neuen zusätzlichen Basisklasse für CircleShape und CircleSectorShape.

    Bin per google "funktion überladen in abgeleiteter klasse" auf eine sehr altes Thema "Basisklassenmethode in abgeleiteter Klasse überladen - Wie?" im C++-Forum gestossen. Da ich dachte, dass ich alle gemeinsamen Variablen und Funktionen in die neue Basisklasse schiebe. Aber die Funktionen sollen in den abgeleiteten Klassen auch überladen werden und das geht scheinbar nur im gleichen Scope (Sichtbarkeitsbereich) bzw. in der gleichen Klasse, sonst werden die Funktionen der Basisklasse überdeckt oder überschrieben.

    Sind eigentlich alle Variablen bzw. Objekte und Funktionen einer Klasse Membervariablen und Memberfunktionen, oder wird zwischen public, protected, private und aus Basisklassen übernommenen Variablen bzw. Objekte und Funktionen entsprechend unterschieden.

    Vielen Dank für alles 👍


Anmelden zum Antworten