Eine Stilfrage



  • Initlisten sind schneller, weil der ganzen MeberVar Chunk per MemCopy in die Instanz kopiert wird. Das ist dann nur ein Copy-Befehl.

    Wenn man alle Member einzeln initialisiert, dann wird pro Member einmal kopiert.

    Initialisierungslisten verwendet man für Klassen, von der sehr viele Instanzen erzeugt werden. Weil der Scheiss so schwierig zu lesen ist, sollte man ihn nur verwenden, wenn nötig.

    Typischer Anwendungsfall für Init-Listen: Vektor-Klassen in 3D Anwendungen. Davon hat man oft ein paar tausend Instanzen.



  • Ich verwende sie immer. Der g++ hat 'ne Kompileroption -Weffc++, die warnt, wenn bestimmte Sachen nicht in der Initialisierungsliste des Konstruktors initialisiert werden. Zumal sind die meisten Anfaengerfehler (auch hier im Forum zu beobachten) meist falsche oder nicht initialisierte Variablen.



  • Peter_Lustig schrieb:

    Initlisten sind schneller, weil der ganzen MeberVar Chunk per MemCopy in die Instanz kopiert wird. Das ist dann nur ein Copy-Befehl.
    Wenn man alle Member einzeln initialisiert, dann wird pro Member einmal kopiert.

    wohl kaum.
    unterschiede hats nur, wenn die konstruktoren oder zuweisungsoperatoren teure dinge tun, wie bei std::string.
    attribute vom typ int oder double kannste drch initialisiererlisten nicht beschleunigen.
    entsprechend sind sie auch nur für sachen wie std::string notwendig und alle weitere ist kür.



  • Wie kommste denn darauf?



  • volkard schrieb:

    Peter_Lustig schrieb:

    Initlisten sind schneller, weil der ganzen MeberVar Chunk per MemCopy in die Instanz kopiert wird. Das ist dann nur ein Copy-Befehl.
    Wenn man alle Member einzeln initialisiert, dann wird pro Member einmal kopiert.

    wohl kaum.
    unterschiede hats nur, wenn die konstruktoren oder zuweisungsoperatoren teure dinge tun, wie bei std::string.

    Es mag sein, das es bei integralen Datentypen kaum oder keinen Unterschied macht. Es ist aber meiner Meinung nach schlechter Stil etwas in einem Fall zu machen, in einem Anderen aber nicht. Zumal es zumindest bei mir die Wahrscheinlichkeit reduziert eine Variable zu vergessen (Ich versuche die Initialisierungsliste immer mit den Membern identisch zu halten, die otionale Warnung wie im gcc scheinbar möglich würde ich mir auch bei anderen Compilern wünschen).

    Initialisierungslisten meiner Meinung nach zwar etwas, aber nicht wesentlich, komplizierter zu lesen.

    cu André



  • asc schrieb:

    Initialisierungslisten meiner Meinung nach zwar etwas, aber nicht wesentlich, komplizierter zu lesen.

    Wenn man die Member schön untereinander gliedert, finde ich das nicht mal weniger übersichtlich als Zuweisungen.

    Aus meiner Sicht können Zuweisungen in Ausnahmefällen okay sein (wenn verpätete Initialisierung oder keine Initialisierung aus Performancegründen notwendig ist), aber für die meisten Member sollte man eigentlich Initialisierungslisten verwenden. Zumal es für gewisse Fälle gar keine Alternative gibt.

    standardstil schrieb:

    was ist daran schlechter?

    Das finde ich total schlimm. Keine Ahnung, was sich die Standard-Leute dabei gedacht haben. Als ob es nicht schon genügend Inkonsistenzen und für jedes Problem mehrere sprachliche Ansätze gäbe - nein, man muss jetzt auch noch Member direkt bei der Deklaration initialisieren können. 🙄



  • Habs nachgemessen. Initlisten sind im Release und im Debug Built bei VC 9 (WinXp Professional 2 GB RAM) schneller. Auch bei integralen Datentypen. Der Speedgain betrug bei meinem Benchmark ca. 10% bei diesen 5 Member Vars

    int      m_a;
        double   m_b;
        int      m_c;
        float    m_d;
        unsigned m_e;
    


  • Nexus schrieb:

    standardstil schrieb:

    was ist daran schlechter?

    Das finde ich total schlimm. Keine Ahnung, was sich die Standard-Leute dabei gedacht haben. Als ob es nicht schon genügend Inkonsistenzen und für jedes Problem mehrere sprachliche Ansätze gäbe - nein, man muss jetzt auch noch Member direkt bei der Deklaration initialisieren können. 🙄

    Stimmt, jetzt könnte man diese komischen initialisierungslisten weg werfen, aber dann würden die alten programme nicht mehr funktionieren. Wenn Member schon immer direkt bei der Deklaration initialisiert worden wären und jetzt Initialisierungslisten dazu kommen würden, dann würden alle Initialisierungslisten dämlich finden.



  • standardstil schrieb:

    Wenn Member schon immer direkt bei der Deklaration initialisiert worden wären und jetzt Initialisierungslisten dazu kommen würden, dann würden alle Initialisierungslisten dämlich finden.

    Dir ist aber schon bewusst, dass es mehrere Möglichkeiten gibt, Member einer Klasse zu initialisieren?

    Von daher finde ich die "einheitliche" Initialisierung bei der Deklaration alles andere als gut. Sobald man eine komplexere Klasse hat, hat diese auch mehrere Konstruktoren. Und sobald dies der Fall ist, ist die Initialisierung gleich in der Klassendefinition nicht mehr so einheitlich. Schlussendlich läuft es darauf hinaus, dass gewisse Member in Konstruktoren, gewisse in der Klasse initialisiert werden. Sehr konsistent.



  • Nexus schrieb:

    standardstil schrieb:

    Wenn Member schon immer direkt bei der Deklaration initialisiert worden wären und jetzt Initialisierungslisten dazu kommen würden, dann würden alle Initialisierungslisten dämlich finden.

    Dir ist aber schon bewusst, dass es mehrere Möglichkeiten gibt, Member einer Klasse zu initialisieren?

    Von daher finde ich die "einheitliche" Initialisierung bei der Deklaration alles andere als gut. Sobald man eine komplexere Klasse hat, hat diese auch mehrere Konstruktoren. Und sobald dies der Fall ist, ist die Initialisierung gleich in der Klassendefinition nicht mehr so einheitlich. Schlussendlich läuft es darauf hinaus, dass gewisse Member in Konstruktoren, gewisse in der Klasse initialisiert werden. Sehr konsistent.

    na, wenn du meinst...



  • PeterLustig schrieb:

    Auch bei integralen Datentypen. Der Speedgain betrug bei meinem Benchmark ca. 10%

    aha. 😮
    meine info war aus einem buch. habs geglaubt, ohne selber zu messen.
    ok, dann werd ich wohl mal vermehrt zu initialisiererlisten greifen.



  • volkard schrieb:

    aha. 😮
    meine info war aus einem buch. habs geglaubt, ohne selber zu messen.
    ok, dann werd ich wohl mal vermehrt zu initialisiererlisten greifen.

    Und das passiert Jemanden der aufschreit, wenn einer von vorzeitigen Optimierungen abrät... ;p



  • ich hab da grad was blödes:

    class Console{
    	private:
    	Size sizeX,sizeY;
    	CHAR_INFO* bufferBegin,bufferEnd;
    	CHAR_INFO* lineBegin,lineEnd;
    	CHAR_INFO* pos;
    	public:
    	Console(){
    		HANDLE hConsole=GetStdHandle(STD_OUTPUT_HANDLE);
    		CONSOLE_SCREEN_BUFFER_INFO csbi;
    		GetConsoleScreenBufferInfo(hConsole,&csbi);
    		sizeX=csbi.dwSize.X;
    		sizeY=csbi.dwSize.Y;
    		bufferBegin=new CHAR_INFO[sizeY*sizeX];
    		bufferEnd=bufferBegin+sizeY*sizeX;
    		lineBegin=bufferBegin;
    		lineEnd=lineBegin+sizeX;
    		pos=lineBegin;
    		//TODO: aktuellen consoleninhalt in den buffer kopieren
    		//cursorposition aus csbi lesen und pos und line* danach 
    		//setzen
    	}
    	void carriageReturn(){
    		pos=lineBegin;
    	}
    	void newLine(){
    		if(lineEnd==bufferEnd){
    			lineBegin=bufferBegin;
    			lineEnd=lineBegin+sizeX;
    			pos-=(sizeY*sizeX-sizeX);
    		}
    		else{
    			lineEnd+=sizeX;
    			lineBegin=lineEnd;
    			pos+=sizeX;
    		}
    		for(CHAR_INFO* p=lineBegin;p!=lineEnd;++p){
    			*p->UnicodeChar=' ';
    			*p->Attributes=7;
    		}
    	}
    	void carriageReturnNewLine(){
    		carriageReturn();
    		newLine();
    	}
    	void writeSpecialChar(char ch){
    	}
    	void writePrintableChar(char ch){
    		*pos=ch;
    		++pos;
    		if(pos==lineEnd)
    			newLine();
    	}
    	void put(char ch){
    		if(ch<16)
    			writeSpecialChar(ch);
    		else
    			writePrintableChar(ch);
    	}
    };
    

    ist alles noch pseudocode, ich experimentiere, wie sich code für eine console anfühlen sollte.

    aber wie mach ich den konstruktor so richtig initialisiererlistenlastig? mir scheint, das würde der ganzen angelegenheit die beine brechen.



  • volkard schrieb:

    ich hab da grad was blödes:
    ...
    aber wie mach ich den konstruktor so richtig initialisiererlistenlastig? mir scheint, das würde der ganzen angelegenheit die beine brechen.

    C-Code in C++ zu wandeln ist nun einmal nicht immer sinnvoll möglich.

    Normalerweise initialisiere ich bei ähnlichen Fällen (aber bei einzelnen Werten) über Rückgabewerte von statische Methoden, da die Initialisierung in so einen Fall eh schon ein Thema für sich ist. Bzw. Kapsel dies in eigene RAII-Objekte.

    Ja, eine einfache Lösung fällt mir hier nicht ein, aber ich muss gestehen das ich zu 95+% mit C++ Schnittstellen hantiere (oder mit Wrappern um eben solche).

    cu André


  • Administrator

    @volkard,
    Kann es überhaupt mehrere Objekt von Console geben? Wäre das nicht perfekt für ein Singleton mit Factory Pattern. In der Fabrikmethode holst du dir ein CONSOLE_SCREEN_BUFFER_INFO und übergibst dieses an den Konstruktor von Console . Dann kann alles in der Initialisierungsliste ausgeführt werden. Wobei man natürlich die Reihenfolge der Initialisierung beachten muss.

    Allerdings lagere ich eigentlich immer gerne Speicherverwaltungsaufgaben aus der Initialisierungsliste raus. Also ein new erfolgt bei mir immer im Konstruktorrumpf und nicht in der Initialisierunsliste.

    Grüssli



  • Dravere schrieb:

    @volkard,
    Kann es überhaupt mehrere Objekt von Console geben?

    bevor deine factory im spiel war, gab es keinen grund dagegen. vielleicht ist es ganz angenehm, ein zweites konsolenfenster für debug-ausgaben oder cerr aufzumachen.

    Wäre das nicht perfekt für ein Singleton mit Factory Pattern. In der Fabrikmethode holst du dir ein CONSOLE_SCREEN_BUFFER_INFO und übergibst dieses an den Konstruktor von Console . Dann kann alles in der Initialisierungsliste ausgeführt werden. Wobei man natürlich die Reihenfolge der Initialisierung beachten muss.

    wenn das nur geschieht, um die initialisiererliste benutzen zu können, ist das nicht ok, finde ich.

    Allerdings lagere ich eigentlich immer gerne Speicherverwaltungsaufgaben aus der Initialisierungsliste raus. Also ein new erfolgt bei mir immer im Konstruktorrumpf und nicht in der Initialisierunsliste.

    jo, geht mir auch so.


  • Administrator

    volkard schrieb:

    wenn das nur geschieht, um die initialisiererliste benutzen zu können, ist das nicht ok, finde ich.

    Naja, die Frage ist eher, ob der Code oberhalb der Zeile 12 im Konstruktor wirklich zu Console gehören sollte. Ist das die Aufgabe vom Konstruktor von Console?
    Wie du selber gesagt hast, wenn jemand die Konsole über STD_ERROR_HANDLE laufen lassen möchte, wäre es besser, wenn der Konstruktor gleich ein gültiges CONSOLE_SCREEN_BUFFER_INFO Objekt erwarten würde.

    Das erstellen eines gültigen CONSOLE_SCREEN_BUFFER_INFO Objektes ist meiner Meinung nach einfach nicht die Aufgabe des Konstruktors von Console .

    Grüssli



  • Dravere schrieb:

    volkard schrieb:

    wenn das nur geschieht, um die initialisiererliste benutzen zu können, ist das nicht ok, finde ich.

    Naja, die Frage ist eher, ob der Code oberhalb der Zeile 12 im Konstruktor wirklich zu Console gehören sollte. Ist das die Aufgabe vom Konstruktor von Console?
    Wie du selber gesagt hast, wenn jemand die Konsole über STD_ERROR_HANDLE laufen lassen möchte, wäre es besser, wenn der Konstruktor gleich ein gültiges CONSOLE_SCREEN_BUFFER_INFO Objekt erwarten würde.

    Das erstellen eines gültigen CONSOLE_SCREEN_BUFFER_INFO Objektes ist meiner Meinung nach einfach nicht die Aufgabe des Konstruktors von Console .

    Grüssli

    ich darf keine lokalen variablen anlegen. hmm.
    die frage ist also, ob innerhalb eines konstruktors gerechnet werden darf!

    da führt mich doch sofort zu

    HashTable::HashTable(size_t size)
    :keys(nextPrimeTwin(size/4*5))
    ,values(nextPrimeTwin(size/4*5))
    {
    }
    

    das ist eine hashtable, deren interne größe ein primzahlenzwilling sein soll, weil das recht gute aussichten auf kollissionsarmut bietet. außerdem sollen keys und values in getrennten speicherbereichen liegen, weil die keys sehr klein und die values sehr lahm sind. bei <=80% füllstand habe ich beste chancen, unter zwei key-zugriffen zu bleiben, und bei tollen keys habe ich beste aussichten, bei ungefähr einem value-zugriff zu bleiben. außerdem ist die berechnung des nächsten primzahlenzwillings eine sehr teure angelegenheit. die members keys und values sind vector-ähnliche klassen, die im konstuktor ihr größe haben wollen. ich möchte keine andere hashtable-struktur wählen nur weil diese hier mich bei den initialisiererlisten verwirrt.

    obiger code ist suboptimal. weg damit.

    HashTable::HashTable(size_t size)
    {
       size_t s=nextPrimeTwin(size/4*5);
       keys=Array<KEY>(s);
       values=Array<VALUE>(s);
    }
    

    naja, auch kacke.

    den benutzer zu zwingen,

    HashTable<...,...> h(nextPrimeTwin(10000));
    

    zu benutzen, fällt auch in die kategogie M.I.S.T..

    also

    HashTable::HashTable(size_t size)
    :realHashTable(nextPrimeTwin(size/4*5))
    {
    }
    

    naja, bedenklich, nur zu diesem zweck ne klasse aufzumachen.

    class PrimeTwin{
    ...
       PrimeTwin(size_t n)
       size_t getValue()
    ...
    HashTable::HashTable(PrimeTwin size)
    :keys(size.getValue())
    ,values(size.getValue())
    

    ich glaub', ich steh im wald.

    das ist anscheinend kein problem, das mit ordentlichem design weggemacht werden kann. es gibt keinen königsweg. sonst hätte schon längst einer eine sprache erfunden, die alles richtig macht, und sie nach einer insel benannt, fürchte ich.

    das konzept mit den initialisiererlisten ist ein wenig fürn popo. warum soll ich nicht die memberkonstruktoren aufrufen dürfen, wann ich mag? es würde doch reichen, daß ich bis zum ende meineskostruktors alle aufgerufen haben muß. und wenn ich die rehenfolge von der deklarationsreihenfolge abweichen lasse, muß ich halt drauf achten, daß das auch mit dem destruktor kompatibel ist, was fast immer der fall ist, weil die members sich gar nicht gegenseitig kennen.



  • volkard schrieb:

    warum soll ich nicht die memberkonstruktoren aufrufen dürfen, wann ich mag?

    Der Grund wird gewesen sein, daß man ein Objekt dann im Konstruktor-Body nicht als ein vollständig initialisiertes Objekt behandeln hätte können; so etwas wie

    void doSomethingWith (MyValueTypeClass& mvtc);
    MyValueTypeClass::MyValueTypeClass (void)
    {
        ...
        doSomethingWith (mvtc);
        ...
    }
    

    wäre dann höchst volatil.

    Tatsächlich ist das natürlich selbst mit Initialisierungslisten nicht vollständig umsetzbar (-> virtuelle Funktionen, typeid, dynamic_cast<>). Initialisierungslisten sind der Versuch eines Workarounds für ein größeres Designproblem: nämlich, daß jede Klasse standardmäßig Wertetypensemantik hat.


Anmelden zum Antworten