Dynamischer Speicher und Klassen



  • Schönen Guten Tag,
    da ich mich vor einiger Zeit mit Windows beschäftigt habe und nun beim Thema dynamische Speicher und Klassen bin, habe ich vor alle drei Gebiete zu verbinden und ein "Übungsprogramm" zu schreiben.

    Inhalt soll sein, dass man ohne Ahnung von Windows zu haben, über die Konsole problemlos Windows-Fenster erzeugen und gestalten kann. Also formatierte Texte ausgeben, Figuren zeichnen und so weiter.

    Allerdings soll es hier nicht um Windows gehen, sondern um das Abspeichern der Daten für die Fenstererstellung.

    Da ich mir das Programmieren selbst beigebracht habe, oder besser gesagt immer noch dabei bin, ist mein größtes Problem wahrscheinlich das Thema Programmierstil. Deshalb wollte ich den Ansatz von meinem Programm einfach mal von euch überprüfen lassen.

    Was sollte ich ändern, was ist unüblich, wie wäre es besser, was ist OK ..., volle Kritik ist erwünscht;-)

    Erst einmal der Ansatz (schon voll funktionsfähig, allerdings noch ohne Kon- und Destruktoren, also Speicherlecks werden noch erzeugt, ist wie gesagt nur ein Ansatz und wird natürlich noch behoben):

    #include <iostream>
    using namespace std;
    
    class clWindow{
    private:
    	class clText{
    	private:
    		string Text;
    		//Weitere Membervariablen für Textformatierung (Textfarbe, Unterstreichung...)
    	public:
    		void setText(string Text){
    			this->Text=Text;
    		}
    		void getText(){
    			cout<<this->Text<<endl;
    		}
    		//weitere Memberfunktionen entsprechend der Variablen...
    	};
    
    	//Weitere Klassen für Figuren, Uhren, Stoppuhren...
    
    	struct knText{
    		knText *nextknText=NULL;
    		clText Text;
    	};
    
    	struct knFigur{
    		knFigur *nextknFigur=NULL;
    		//Klassenobjekt für Figuren...
    	};
    
    	struct knFenster{
    		knText *nextknText=NULL;
    		knFigur *nextknFigur=NULL;
                    knFenster *nextknFenster=NULL;
    		//Weitere Zeiger für Uhren... 
    	};
    
    	knFenster *wuFenster=NULL;
    	knText *Textposition=NULL;
    
    	void Textfelderstellen(){
    		Textposition=wuFenster->nextknText=new knText;
    	}
    
    public:
    	void Fenstererstellen(){ //Kommt in Konstruktor, weitere Funktionen für Windows...
    		wuFenster=new knFenster;
    	}
    
    	void Texteingabe(string Text){
    		static bool Textfeld=0;
    		if(!Textfeld){
    			Textfelderstellen();
    		}
    		Textposition->Text.setText(Text);
    		Textposition=Textposition->nextknText=new knText;
    		Textfeld=1;
    	}
    	//Weitere Funktionen für Textformatierung und erstellen von Figuren, Timern...
    
    	void Textausgeben(){ //Erstmal zum Test
    		knText *help=wuFenster->nextknText;
    		while(help!=NULL){
    			help->Text.getText();
    			help=help->nextknText;
    		}
    	}
    	//Weitere Funktionen zum Durchlaufen des Speichers...
    };
    
    int main(int argc, char *argv[]) {
    
    	clWindow w;
    	w.Fenstererstellen();//Später an anderer Stelle...
    
    	w.Texteingabe("Hallo");
    	w.Texteingabe("World");
    
    	w.Textausgeben();//Nur zum Test...
    
    	return 0;
    }
    

    Code ist natürlich aus den verschiedenen .h und .cpp Dateien zusammenkopiert.

    Meine Idee des Speicheraufbaus:
    Man erstellt eine Klasse (im Ansatz Klasse "w") für einen Fensterkomplex, man kann also verschiedene Objekte erzeugen, jedes Objekt beinhaltet die Daten für die Erstellung von einem oder mehreren Fenster, man kann also später auswählen welchen "Fensterkomplex" man anzeigen möchte, wie viele Fenster man haben möchte und was in jedem Fenster angezeigt wird

    Beim erstellen der Klasse wird eine Liste begonnen, bei der jeder Knoten ("knFenster" genannt) für ein Fenster steht (momentan das Beginnen der Liste noch über "Fenstererstellen()")

    Von jedem "knFenster" gehen Zeiger auf Listen die den Inhalt des Fensters beinhalten, momentan auf eine Liste die den Text und dessen Formatierung beinhaltet ("knText") und auf eine Liste die die Daten zum erstellen von Figuren im Fenster beinhalten soll ("knFigur", allerdings noch nicht ausprogrammiert)

    Bei der Liste für den Text ist in jedem Knoten ein Objekt ("Text") der Klasse "clText" angelegt.

    Die Klasse "clText" beinhaltet momentan als Variablen einen String "Text" für den Text und soll später weitere Variablen erhalten (z.B. Textfarbe, Textposition), und natürlich die dazugehörigen Getter und Setter.

    Das Eingaben der Textzeilen in die Liste erfolgt über die Funktion "Texteingabe()", bei dieser wird über die static bool "Textfeld" überprüft ob die Funktion bereits aufgerufen wurde (also ob die Liste für den Text bereits begonnen ist), oder nicht (und somit der Anfang der Liste erstmal erstellt werden muss)

    Ich hoffe einfach mal ihr versteht den Code, falls ihr meine zugegeben etwas verwirrende Erklärung nicht nachvollziehen könnt;-)
    Fragen sind immer willkommen...

    Wie gesagt, Code könnt ihr einfach kopieren, funktionier auch fehlerfrei.
    Nun Hoffe ich auf die ein oder andere Kritik von euch!
    Schon im Voraus vielen Dank, und ein schönes Wochenende!
    LG John.



  • ...



  • Hallo,
    auf jeden Fall vielen Dank für deine Mühe.
    Wobei du mir ehrlich gesagt nicht sonderlich weiterhilft, wenn du mir einfach einen völlig anderen Code gibst. (So mal ich diesen nicht vollständig verstehe, und mein Compiler diesen nicht schluckt)...

    Da wäre es mir lieber gewesen, und du hättest dir einiges an Arbeit gespart, wenn du einfach in 5 Zeilen gesagt hättest, was ich verändern sollte;-)
    Nach mehr habe ich ja auch nicht gefragt:

    John1 schrieb:

    Was sollte ich ändern, was ist unüblich, wie wäre es besser, was ist OK ...

    LG John.



  • @John1
    Benutze keine ungarische Notation (Allen Klassen cl und allen structs kn voran zu stellen macht den Code schlechter lesbar, nicht besser).

    Variablennamen, die auf _t enden sind vom Posix-Standard reserviert.

    Ich würde erwarten, dass eine Funktion getText mir den Text gibt. Stattdessen wird dieser cout gegeben.

    Vermeide allgemein new und delete. Das ist unnötig fehleranfällig. Benutze stattdessen vector, unique_ptr und wenn es unbedingt sein muss shared_ptr.

    Es sieht so aus, als wolltest du mit "knFigur *nextknFigur=NULL;" eine Arte verkette Liste bauen. Das gibt es schon fertig. Es heißt in 95% der Fälle benutzt man std::vector, in 4% std::map und den Rest (wie std::list) in 1% der Sonderfälle.

    Wenn jemand "clWindow cl2 = cl;" schreibt werden dir deine Zeiger um die Ohren fliegen. Mit Copy Constructor, Move Constructor, Copy Assignment Operator, Move Assignment Operator kriegt man das wieder hin. Allgemein solltest du versuchen, dass man mit deinen Klassen alles machen kann, was man auch mit anderen Typen wie int machen kann.

    @Swordfish
    Ich bin inzwischen an dem Punkt, wo ich Vererbung vermeide wenn immer es möglich ist, aber darüber lässt sich streiten. Vererbung zerstört Kapselung, ist sehr unflexibel und inperformant. In diesem Fall ist es zusätzlich auch noch völlig unnötig.

    add_widget sollte direkt einen unique_ptr nehmen. Ansonsten macht jemand sowas und es stürzt ab:

    textbox_t t("Hello, Textbox!");
    window.add_widget(&t);
    

    Wenn die Funktion einen unique_ptr nimmt pasiert so ein Missverständnis nicht.



  • nwp3 schrieb:

    add_widget sollte direkt einen unique_ptr nehmen. Ansonsten macht jemand sowas und es stürzt ab:

    textbox_t t("Hello, Textbox!");
    window.add_widget(&t);
    

    Wenn die Funktion einen unique_ptr nimmt pasiert so ein Missverständnis nicht.

    Ne eben nicht. Jedenfalls nicht nach hustbaers Code (den "originalen" hab ich mir nicht durchgelesen, war mir zu kompliziert), dort wird ja explizit kopiert. Auf der anderen Seite gibts ne Reihe Memory-Leaks beim aufrufen der add_widget Funktion.



  • ...



  • Swordfish schrieb:

    nwp3 schrieb:

    @Swordfish
    Ich bin inzwischen an dem Punkt, wo ich Vererbung vermeide wenn immer es möglich ist, aber darüber lässt sich streiten. Vererbung zerstört Kapselung, ist sehr unflexibel und inperformant. In diesem Fall ist es zusätzlich auch noch völlig unnötig.

    Wie erreiche ich sonst, daß ich alle Widgets in einen gemeinsamen Container stecken kann um allen auf die selbe weise zu sagen: "Zeichne dich!"?

    Idealerweise gar nicht. Spendiere jedem Widget einen eigenen Container. Das macht dich viel freier in den Funktionen, denn die Funktion "Zeichne dich" reicht nicht aus, manchmal brauchst du "Ändere Farbe", manchmal "Ändere Text", aber nicht alle Widgets haben eine Farbe/Text/Icon/Callback/... . Dann kämpfst du nur gegen die Vererbungshierarchie. Ich hatte noch vergessen, dass du final und override benutzen solltest.
    Eine Alternative ist per Template und Type-Erasure wenigstens die Vererbung zu verstecken. Non-Virtual-Interface geht in dieselbe Richtung. Hilft meiner Meinung nach nur partiell. Widgets sind halt verschieden, wenn man sie in ein einheitliches Interface packt wird es falsch, egal wie man es anstellt.
    Ich versuche gerade einen MultiVector zu bauen, der so ähnlich wie ein Tupel funktioniert, nur dass er statt einzelner Objekte pro Typ einen vector von Objekten pro Typ enthält. Funktioniert aber leider noch nicht.


  • Mod

    Spendiere jedem Widget einen eigenen Container.

    Und dann muss ich durch alle Container durchgehen wenn ich eine Sache mit allen machen will?

    Das macht dich viel freier in den Funktionen, denn die Funktion "Zeichne dich" reicht nicht aus, manchmal brauchst du "Ändere Farbe", manchmal "Ändere Text", aber nicht alle Widgets haben eine Farbe/Text/Icon/Callback/... .

    Macht man das nicht mit einer TextContainer Klasse, von der man dann ableitet, falls das Widget Text enthält?



  • Arcoth schrieb:

    Das macht dich viel freier in den Funktionen, denn die Funktion "Zeichne dich" reicht nicht aus, manchmal brauchst du "Ändere Farbe", manchmal "Ändere Text", aber nicht alle Widgets haben eine Farbe/Text/Icon/Callback/... .

    Macht man das nicht mit einer TextContainer Klasse, von der man dann ableitet, falls das Widget Text enthält?

    Und dann? Wenn TextContainer von Container ableitet und du alles in einen vector<Container *> stopfst weißt du nicht mehr welche Container TextContainer sind und welche nicht. Wenn du TextContainer in einen extra vector steckst endest du wieder mit vielen vectoren, über die du einzeln drüber iterieren musst und die Vererbung hat nichts gebracht.



  • Hallo,
    gleich am Anfang Vielen Dank an nwp3, solche Hinweise helfen einen dann doch am meisten weiter (unique_ptr kannte ich noch nicht, da wird new/delete natürlich überflüssig:-)

    Zu der Container Frage, mir erscheinen mehrere auch deutlich besser geeignet, zum einen weil mir auf Anhieb zehn verschiedene Widgets einfallen und man dann schnell auf mehrere 100 Elemente im Vektor kommt. Wenn man jetzt im Millisekundentakt ein Widget ändern muss (z.B. Uhrzeit) und dafür jedesmal mehrere 100 Elemente nach dem einen Richtigem durchsuchen muss, kann ich mir nicht vorstellen, dass das noch in der Geschwindigkeit möglich ist.

    Ansonsten habe ich auch in Swordfish`s Code Durchblick gefunden, mein einziges Problem war tatsächlich, dass ich das:

    Swordfish schrieb:

    textbox_t( std::string const & text ) : id{ ++widget_t::id }, text{ text }

    bisher nur so kannte:

    textbox_t( std::string const & text ) : id( ++widget_t::id ), text( text )
    

    Gibt es da einen Unterschied oder sind das Synonyme?

    Zum Schluss noch zwei letzte Fragen:
    Swordfish`s Vorschlag entspricht in etwa dem was ich vor habe (das mit mehreren Containern ist ja nur eine Kleinigkeit). Nur soll es eine weitere Klasse geben (z.B class windows - man beachte das "s"). Diese soll mehrere Objekte vom Typ window verwalten und man soll nur über diese Klasse z.B neue window-Objekte oder textbox/image-Objekte erzeugen können.

    Sollte ich die verschiedenen window-Objekte ebenfalls über einen Vektor in der windows-Klasse abspeichern, oder gibt es da eine elegantere Lösung?

    Ist der Ansatz korrekt, dass außer die windows-Klasse alle anderen Klassen nur private Memberfunktionen/-variablen besitzen und ich diese Klassen als friend der windows-Klasse angebe?
    (Man soll ja nicht direkt auf die anderen Klassen zugreifen können)

    Vielen Dank im Voraus, LG John.


Anmelden zum Antworten