Hallo Leute,
kürzlich habe ich mir mal ins Bein geschossen, weil die qt-Bibliothek standardmäßig alle Verweise auf andere Objekte innerhalb eines QObject automatisch im Destruktor löscht (mittels delete). Kann man dieses Verhalten auf einfache Art ändern? Die mir vorliegende Dokumentation schweigt sich darüber aus und vermutet noch nicht einmal, dass dieses Standardverhalten eigentlich völlig krank ist.
Danke, Torsten
Torsten Werner wrote:
Hallo Leute,
Hallo,
kürzlich habe ich mir mal ins Bein geschossen, weil die qt-Bibliothek standardmäßig alle Verweise auf andere Objekte innerhalb eines QObject automatisch im Destruktor löscht (mittels delete). Kann man dieses Verhalten auf einfache Art ändern? Die mir vorliegende Dokumentation schweigt sich darüber aus und vermutet noch nicht einmal, dass dieses Standardverhalten eigentlich völlig krank ist.
Meine Doku sagt da folgendes:
[schnipp] QObject::~QObject () [virtual] Destructs the object, deleting all its child objects.
All signals to and from the object are automatically disconnected.
Warning: All child objects are deleted. If any of these objects are on the stack or global, your program will sooner or later crash. We do not recommend holding pointers to child objects from outside the parent. If you still do, the QObject::destroyed() signal gives you an opportunity to detect when an object is destroyed. [schnapp]
Nur so ein Idee: falls du deine Child-Widgets behalten möchtest, könntest du ihnen ja vorher ein anderes Parent-Widget geben (QObject::reparent()).
BTW: Qt ist nicht das einzige GUI-Toolkit, das dieses Verhalten hat. Ich habe damit auch noch keine Probleme gehabt ...
Danke, Torste
Jens
Am Dienstag, dem 26. März 2002 um 20:43:44, schrieb Jens Lorenz:
Warning: All child objects are deleted. If any of these objects are on the stack or global, your program will sooner or later crash.
Ja das kenne ich, aber wie verhindere ich es auf *einfache* Art? Ein Compiler-Flag oder ähnliches suche ich.
Nur so ein Idee: falls du deine Child-Widgets behalten möchtest, könntest du ihnen ja vorher ein anderes Parent-Widget geben (QObject::reparent()).
Gibt es in meiner Dokumentation nicht, es gibt nur QWidget::reparent() .
BTW: Qt ist nicht das einzige GUI-Toolkit, das dieses Verhalten hat. Ich habe damit auch noch keine Probleme gehabt ...
Schön für dich, aber es ist trotzdem krank und verhindert sauberes Programmieren in C++; Stichwort smart pointer, "resource acquisition is initialization" etc.
Torsten
P. S.: Habe gerade 'void QObject::removeChild(QObject* obj)' gefunden, probiere es demnächst mal aus. Ist außerdem trotzdem nicht so einfach wie der von mir erhoffte Compiler-Flag.
On Tuesday, 26. March 2002 23:59, Torsten Werner wrote:
P. S.: Habe gerade 'void QObject::removeChild(QObject* obj)' gefunden, probiere es demnächst mal aus. Ist außerdem trotzdem nicht so einfach wie der von mir erhoffte Compiler-Flag.
Du wirst aber bei sowas wie QListView aufpassen müssen, wenn du die Child-Objekte dort nicht über "delete" löschst sondern so wie oben oder mit remove() dann ist Instabilität vorprogrammiert. Für Listen in Qt (für 2.x: QList, 3.x: QPtrList) gibt es setAutoDelete().
Hab auch schon meine Diskussion mit den Trollen dazu (QListView) gehabt, aber im großen und ganzen find ich es gut, daß ein QObject "seine" Kinder übernimmt. Führt zu kürzerem Code, ist (wenn man einmal diese Sichtweise übernommen hat) vorhersehbar, und.... macht Mentoring in #kde.de leichter :)
Hast du dir schon QObjectCleanupHandler angesehen? Oder QObject::childEvent()?
Ansonsten kann ich nur eine Sprache empfehlen, die nicht wie C++ versucht einen Spagat zwischen Assembler und Highlevel hinzubekommen... Für Qt2 gibt's gute Ruby-Bindings, Python soll auch dafür nutzbar sein.
Josef Spillner
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On Tuesday 26 March 2002 14:00, Torsten Werner wrote:
kürzlich habe ich mir mal ins Bein geschossen, weil die qt-Bibliothek standardmäßig alle Verweise auf andere Objekte innerhalb eines QObject automatisch im Destruktor löscht (mittels delete). Kann man dieses Verhalten auf einfache Art ändern? Die mir vorliegende Dokumentation schweigt sich darüber aus und vermutet noch nicht einmal, dass dieses Standardverhalten eigentlich völlig krank ist.
Meine Programme funktionieren ganz gut mit diesem Verhalten. Eigentlich funktionieren sie nur _weil_ es da ist. Du willst nicht wirklich einige dutzend Objekte selbst aufraeumen von denen die Haelfte automagisch erzeugt wurde. Wenn Du nicht willst, dass ein QObject die Verantwortung fuer ein anderes QObject uebernimmt, dann sag' ihm nicht, dass es da ist: lege einen Verweis an, aber mach es nicht zum parent. Ansonsten ist der Normalfall bei Qt die Kopie (QString ist z.B. so optimiert, dass eine Kopie kaum Speicher braucht) oder eine Signal-Slot-Verbindung (die werden automatisch aufgeraeumt, aber die Objekte bleiben bestehen).
Sehr viele Probleme lassen sich dadurch loesen, dass man eine Signal-Slot-Verbindung aufbaut und das signalisierende Objekt seinen this-Pointer mitliefert.
Ansonsten versuch es mal mit Sprachen, die einen Garbage Collector haben: SmallTalk, Ruby, Java, etc. - da entfallen diese Probleme.
Konrad
- -- Killing is stupid; useless! -- McCoy, "A Private Little War", stardate 4211.8
Danke für eure Hinweise. Mir ist klar geworden, dass dieses von mir als krank bezeichnete Verhalten wahrscheinlich in der Bibliothek intern vorausgesetzt wird. Deswegen kann es keinen einfachen Weg geben, es abzuschalten.
Ein guter Grund, den GUI-Code von der eigentlichen Anwendungen sauber zu kapseln und darin mit den QT-Macken zu leben, auch wenn ich sie hässlich finde. Oder ich schaue mir mal gtkmm an... ;-) Es scheint aber keine Windows-Version davon zu geben. :-(
Am Mittwoch, dem 27. März 2002 um 06:20:58, schrieb Konrad Rosenbaum:
Meine Programme funktionieren ganz gut mit diesem Verhalten. Eigentlich funktionieren sie nur _weil_ es da ist.
Aha, C++-Programme, die kein QT benutzen, funktionieren dann bei dir nie?
Sehr viele Probleme lassen sich dadurch loesen, dass man eine Signal-Slot-Verbindung aufbaut und das signalisierende Objekt seinen this-Pointer mitliefert.
Auch so eine Krücke, die schön gedacht, aber schlecht in QT implementiert ist.
Ansonsten versuch es mal mit Sprachen, die einen Garbage Collector haben: SmallTalk, Ruby, Java, etc. - da entfallen diese Probleme.
Ich habe meine Zweifel, dass diese Garbage Collectors wirklich das Wahre sind. Nun gut, sie sind zumindest anfängerfreundlich. Wenn ich in solchen Sprachen programmiere, dann kenne ich das Verhalten und es stört mich dann auch nicht bzw. ich nutze es zu meinem Vorteil.
In C++ erwarte ich nicht, dass eine Bibliothek das Speichermanagement eines Teils meiner Objekte übernimmt (nur die, die von QObject abgeleitet sind) und es dann noch falsch macht (delete für ein Stack-Objekt aufrufen!). Normalerweise verwende ich kaum Zeiger und wenn doch, dann nicht in ihrer Reinform, sondern z. B. http://www.boost.org/libs/smart_ptr/index.htm .
Torsten
On Wed, Mar 27, 2002 at 11:04:01AM +0100, Torsten Werner wrote:
Danke für eure Hinweise. Mir ist klar geworden, dass dieses von mir als krank bezeichnete Verhalten wahrscheinlich in der Bibliothek intern vorausgesetzt wird.
Ich finde diese Art der GarbageCollection sehr interessant, da wirklich Speicher gespart wird (Klassen die nicht mehr gebraucht werden, werden wirklich _sofort_ gelöscht) es aber dennoch programmierfreundlich ist. Was willst du denn eigentlich genau machen, damit es nötig wird, dem Elternobjekt das Löschen des Kindes zu verbieten?
Ciao, Tobias
Am Mittwoch, dem 27. März 2002 um 17:20:16, schrieb Tobias Koenig:
Ich finde diese Art der GarbageCollection sehr interessant, da wirklich Speicher gespart wird (Klassen die nicht mehr gebraucht werden, werden wirklich _sofort_ gelöscht) es aber dennoch programmierfreundlich ist.
Da Zeiger als böse gelten, lege ich meine Objekte am liebsten auf dem Stack an oder verwalte sie zur Not mit einem Smart-Pointer. In beiden Fällen ist das Löschen durch QT fatal - zlib ich hör dir trapsen...
Stell dir vor, die Standard-C-Bibliothek würde die beiden char*-Argumente von fopen() nach Benutzung einfach mit free() freigeben. Kein Programmierer würde dieses Verhalten für gesund halten, aber QT darf solchen Mist machen!
Torsten
Ja, steht ja schon oben. Ich kann mir nichts genaues drunter vorstellen, bei 'Hello World' brauchte ich das noch nicht ;-)
Vielleicht in die Richtung: Wenn ich ein Objekt nicht mehr brauche, wird es destruktiert um den Speicherplatz wieder freizugeben (oder wozu sonst?) und es landet im Garbage Kollektor. Was landet da dort drinne? Das Objekt? dann habe ich ja keinen Speicheplatz freigegeben. Was also? Und warum?
Mit freundlichen Grüßen
Jens Puruckherr
Am Mittwoch, dem 27. März 2002 um 11:35:29, schrieb Jens Puruckherr:
Ja, steht ja schon oben.
Ein C++-Beispiel (ungetestet):
void funktion() { int* a(new int); // irgendwas mit *a anfangen: *a = 5; ... }
C++ gibt den Speicherplatz am Ende der Funktion nicht frei, ein automatischer Garbage Collector täte das, oft aber erst irgendwann und nicht sofort am Ende der Funktion. Einfache Lösung in C++:
void funktion() { std::auto_ptr<int> a(new int); // irgendwas mit *a anfangen: *a = 5; ... }
Hier wird der Speicher genau am Ende der Funktion freigegeben. C++ kann auch mehr als nur Speicher 'garbage zu collecten':
void funktion() { boost::scoped_ptr<FILE> a(fopen("datei"), fclose); // irgendwas mit a anfangen: fprintf(a, "Hallo Welt!\n"); ... }
Hier wird fclose(a) automatisch am Ende der Funktion aufgerufen.
Torsten
Am Mittwoch 27 März 2002 11:56 schrieb Torsten Werner:
void funktion() { boost::scoped_ptr<FILE> a(fopen("datei"), fclose); // irgendwas mit a anfangen: fprintf(a, "Hallo Welt!\n"); ... }
Hier wird fclose(a) automatisch am Ende der Funktion aufgerufen.
Zu leiden der Übersichtlichkeit, wie ich finde, und mit einem klitzekleinen Overhead. Aber immer noch besser als ein Garbage Collector der irgendwann losrennt und dann auch mal bei großen Programmen 100% CPU auffrisst.
Stephan
Am Mittwoch, dem 27. März 2002 um 20:52:53, schrieb Stephan Goetter:
Am Mittwoch 27 März 2002 11:56 schrieb Torsten Werner:
void funktion() { boost::scoped_ptr<FILE> a(fopen("datei"), fclose); // irgendwas mit a anfangen: fprintf(a, "Hallo Welt!\n"); ... }
*Hüstel* - obiges Beispiel funktioniert 'nur' mit shared_ptr und nicht mit scoped_ptr und fopen() hat 2 Argumente, sorry... Folgendes ist getestet:
#include "boost/shared_ptr.hpp" #include <cstdio> int main() { boost::shared_ptr<FILE> dat(fopen("/etc/hosts", "r"), fclose); char line[256]; fgets(line, sizeof(line), dat.get()); puts(line); return 0; }
Zu leiden der Übersichtlichkeit, wie ich finde, und mit einem klitzekleinen Overhead.
Wieso leidet in meinem Beispiel die Übersichtlichkeit? Spätestens wenn Ausnahmen ausgelöst werden oder ein vorzeitiges return benutzt wird, ist obige Lösung eleganter als explizites Aufrufen von fclose(). Mit dem Overhead verstehe ich auch nicht. Das shared_ptr-Template benutzt keine Vererbung und kann von einem guten Compiler komplett 'ge-inline-d' werden, oder?
Torsten
Am Donnerstag 28 März 2002 22:17 schrieb Torsten Werner:
Am Mittwoch, dem 27. März 2002 um 20:52:53, schrieb Stephan Goetter:
Am Mittwoch 27 März 2002 11:56 schrieb Torsten Werner:
*Hüstel* - obiges Beispiel funktioniert 'nur' mit shared_ptr und nicht mit scoped_ptr und fopen() hat 2 Argumente, sorry... Folgendes ist getestet:
Hat da etwa jemand die Übersicht verloren? ;)
Zu leiden der Übersichtlichkeit, wie ich finde, und mit einem klitzekleinen Overhead.
Wieso leidet in meinem Beispiel die Übersichtlichkeit? Spätestens wenn Ausnahmen ausgelöst werden oder ein vorzeitiges return benutzt wird, ist obige Lösung eleganter als explizites Aufrufen von fclose().
Ich meinte damit das man die Linearität des Programmes nicht mit einem Blick erfassen kann.
Mit dem Overhead verstehe ich auch nicht. Das shared_ptr-Template benutzt keine Vererbung und kann von einem guten Compiler komplett 'ge-inline-d' werden, oder?
Das hört man bei C++ immer wieder. Feature XYZ kann doch vom Compiler... das hat Bjarne auch immer geschrieben, das man theoretisch das so implementieren kann, das kein Overhead... aber die Praxis sieht meist anders aus als die graue Theorie. Und gute Compiler sind nunmal Mangelware.
Stephan
Am Freitag, dem 29. März 2002 um 12:09:07, schrieb Stephan Goetter:
Hat da etwa jemand die Übersicht verloren? ;)
Nö, ich hatte es einfach nicht getestet, das stand aber in meiner Originalnachricht auch drin. Es sollte nur meine Argumente illustrieren...
Ich meinte damit das man die Linearität des Programmes nicht mit einem Blick erfassen kann.
Bei objektorientierten Programmen kann man das meisten nicht. Was ist schlimm daran? Gute Namen sind mir mehr wert als lineare Programmflüsse. Es ist schließlich kein Buch, sondern Software.
Das hört man bei C++ immer wieder. Feature XYZ kann doch vom Compiler... das hat Bjarne auch immer geschrieben, das man theoretisch das so implementieren kann, das kein Overhead... aber die Praxis sieht meist anders aus als die graue Theorie. Und gute Compiler sind nunmal Mangelware.
Im Zusammenhang mit blitz++ und expression templates hatte ich mal ein Beispiel gesehen, wo sich ein in C++ elegant formulierter (nichttrivialer) Algorithmus vom schlecht lesbaren aber handoptimierten C-Code durch nur genau eine Assembleranweisung unterschied. Welcher Compiler es war - da müsste ich jetzt lügen. Prinzipiell bevorzuge ich zuverlässigen und wartbaren Code vor optimierten Code.
Torsten
Hallo,
On Wednesday, 27. March 2002 11:35, Jens Puruckherr wrote:
Vielleicht in die Richtung: Wenn ich ein Objekt nicht mehr brauche, wird es destruktiert um den Speicherplatz wieder freizugeben (oder wozu sonst?) und es landet im Garbage Kollektor. Was landet da dort drinne? Das Objekt? dann habe ich ja keinen Speicheplatz freigegeben. Was also? Und warum?
Im Garbage Collector landet nichts. Der GC ist sozusagen eine Routine in der Laufzeitumgebung (z.B. JVM, CIL/mint bei mono). Der GC ist dafür zuständig, nicht mehr benötigen Speicher freizugeben. Inwiefern der das kann, das ist der Knackpunkt. Es gibt:
- konservative GC's. Ein Beispiel ist der Boem GC, der z.B. in der gcc 3.x eingesetzt wird (gcj). Dabei wird der Speicher "abgesucht", und alles was verdächtig nach benutztem Pointer aussieht wird lieber in Ruhe gelassen. Dabei wird aber eher mehr stehen gelassen als sein muß (-> mehr Speicherbedarf).
- präzise GC's. Dabei sind die Möglichkeiten zur Speicherallokierung stark mit dem GC verknüpft, so daß man immer weiß was man entfernen darf (IIRC).
- inkrementelle GC's. Das ist was ganz witziges, dabei werden alle Speicherbereiche auf die häufiger/langfristiger zugegriffen wird nach hinten in eine Queue geschoben, und vorne bleibt das stehen wo es sich eher mal lohnt was wegzulöschen. Also ein Scheduling-Verfahren. Zusammen mit einem inkrementellen JITer recht interessant.
So, das war mein gesamtes Halbwissen zum Thema, partiell aufgewertet durch einen de Icaza-Vortrag, der über den Ximian-Partner Intel Einblick in deren GC hatte :-)
Josef Spillner
Ja, Danke erst mal.
Mein Verdacht hat sich also bestätigt. Wenn der Garbage Collector aber immer eingebaut ist in den Compiler/VM, dann brauche ich mir doch darüber keine Gedanken machen - ausser mich zu ärgern,das er nicht richtig funktioniert evtl. Oder kann ich ihn (je nach Sprache natürlich) geschickt ausnutzen und 'programmieren'? Torsten hat ja schon Möglichkeiten gezeigt. Macht man das so? Muß man sich wirklich Gedanken darüber machen?
Bin ich froh, bisher nur PHP zu können zu müssen....aber Java steht vor der Tür...huhuahua
Mit freundlichen Grüßen
Jens Puruckherr
lug-dd@mailman.schlittermann.de