Problem mit Wrapper-Klasse für Handles
-
Da man erstellte GDI-Objekte (auf die man mit Handles zugreift) auch immer manuell löschen muß, hab ich mir mal eine einfache Wrapper-Klasse dafür geschrieben, die folgendermaßen aussieht:
template<class T> class GDIObject { public: GDIObject (T h) { handle=h; } ~GDIObject () { DeleteObject (handle); } operator T () { return handle; } private: T handle; };Wenn ich nun zum Beispiel ein Bitmap erstellen will, schreibe ich einfach
GDIObject<HBITMAP> bitmap=CreateBitmap (200, 100, 1, 1, NULL);Doch das Problem, das ich jetzt habe (eigentlich ist es kein Problem, sondern nur eine kleine Unsauberkeit): Wenn jemand dem Konstruktor nicht die Create-Funktion, sondern ein bereits vorhandenes Handle übergibt, dann wird dies auch in genau dem Moment gelöscht, wo die GDIObject-Variable aus dem Stack geworfen wird, was ja nicht in jedem Fall gewollt ist. Beispiel:
void Funktion (GDIObject<HBITMAP> bmp); //... HBITMAP bitmap=CreateBitmap (200, 100, 1, 1, NULL); Funktion (bitmap); //Ab hier ist das Handle nicht mehr gültig. BITMAP bm; GetObject (bitmap, sizeof (bm), &bm); //Und hier gibt es einen Fehler.Deshalb würde ich gern wissen: Gibt es eine Möglichkeit, herauszufinden, ob der übergebene Wert gerade frisch aus einer Create-Funktion kommt oder ob schon eine andere Variable auf ihn zugreift?
Das einfachste wär ja hier, auch die Create-Funktionen zu wrappen und in die Klasse zu packen, dann könnte ich das über eine Bool-Variable prüfen: Wurde der Copy-Konstruktor (oder =-Operator) aufgerufen, muß das Handle gültig bleiben, bei der Create-Memberfunktion wird das Handle im Destruktor gelöscht. Aber das Problem ist hierbei: Es handelt sich um eine Template-Klasse und jedes GDI-Objekt (Bitmap, Font, Brush etc.) hat eigene Create-Funktionen.
Wie kann ich es also realisieren, daß die Handles nur in den Objekten gelöscht werden, wo sie auch extra fürs Objekt erstellt und nicht von einem bereits vorhandenen Objekt oder Handle übernommen wurden?
-
Das gleiche Problem hast du zB auch bei Smart Pointern. Hier behilft man sich ua mit Shared Pointern. Dh, es wird intern per Reference Counting gezählt, wieviele Kopien im Umlauf sind. Und erst beim dtor der letzten Kopie wird auch der zugehörige Speicherbereich freigegeben. Das Prinzip kannst du auch auf Handles und deine Klasse übertragen. Fragt sich nur, ob das den Aufwand lohnt. Du kannst als Parameter ja auch einfach nur eine Art GDIObjectCopy<T> Objekt übergeben, welches sich nur über ein GDIObject<T> erzeugen lässt, und wo DeleteObject im GDIObjectCopy<T> dtor nicht aufgerufen wird.
-
Ja, das mit der Referenzzählung würde vielleicht funktionieren, aber nur, wenn man ausschließlich mit GDIObject arbeiten würde. Dann würde das diese Klasse übernehmen. Aber sie kann ja nicht wissen, wie viele lose HBITMAP-Variablen im Programm noch so existieren.
Du kannst als Parameter ja auch einfach nur eine Art GDIObjectCopy<T> Objekt übergeben, welches sich nur über ein GDIObject<T> erzeugen lässt, und wo DeleteObject im GDIObjectCopy<T> dtor nicht aufgerufen wird.
Das hab ich jetzt nicht verstanden. Aber wie auch immer. Es soll so sein, daß bei
GDIObject<HBITMAP> bitmap=CreateBitmap (...);im Destruktor DeleteObject aufgerufen wird, während das nicht passiert, wenn ich sowas hier mache:
HBITMAP bitmap=CreateBitmap (...); GDIObject<HBITMAP> gdiBitmap=bitmap;Oder noch besser: Zweiteres soll gar nicht funktionieren, da die Klasse ohnehin nur dafür da ist, komplett neue GDI-Objekte zu repräsentieren. Gibt es keine Möglichkeit, die Übergabe in den Parametern durch eine direkte Variable (HBITMAP) zu unterbinden und nur Funktionen zuzulassen, die so einen Wert zurückgeben? Also ungefähr so:
HBITMAP bitmap=CreateBitmap (...); GDIObject<HBITMAP> gdiBitmap1=CreateBitmap (...); //Alles O.k. GDIObject<HBITMAP> gdiBitmap2=bitmap; //Compilerfehler: Der Wert darf nur aus einer Funktion, nicht aus einer vorhandenen Variable kommen. GDIObject<HBITMAP> gdiBitmap2=gdiBitmap1; //Compilerfehler: Der Wert darf nur aus einer Funktion, nicht aus einer vorhandenen Variable kommen.Oder muß ich mich damit begnügen, in der Erläuterung zur Klasse zu schreiben: "Achtung: Nur zum Erstellen neuer GDI-Objekte durch Create-Funktionen gedacht. Übergeben Sie dem Konstruktor niemals direkt eine Handle-Variable!"?
P.S.: Das Übergeben einer vorhandenen GDIObject-Variable könnte man ja noch kontrollieren, indem man den Copy-Konstruktor überschreibt und explizit auch einen mit GDIObject-Parameter zuläßt. Dann müßte man nur darauf aufmerksam machen, daß direkte HBITMAPs nicht durch Variablen übergeben werden sollen.
-
Mach eine private Deklaration des copy-Constructors ohne Kode, dann erzeugt seine Verwendung von außen einen Compiler-Error. Und an Funktionen übergibst Du Dein GDIObject ausschließlich als Referenz bzw. Zeiger.
-
Wie schon gesagt wurde: mach deine Wrapper-Klasse nicht-kopierbar und nicht-zuweisbar (copy Konstruktor & assignment Operator private deklarieren).
Und mach den Konstruktor "explicit".
Ggf. kannst du noch ein bool mitgeben welches angibt ob der Dtor das Handle freigeben soll oder nicht.Wenn du dann reference counting, bzw. shared ownership allgemein brauchst, kannst du ja einach boost::intrusive_ptr<GDIObject<HBITMAP> > bzw. boost::shared_ptr<GDIObject<HBITMAP> > rumreichen -- ich sehe keinen besonderen Sinn darin diese Funktionalität nochmals auszuprogrammieren.
-
O.k., danke für die Hinweise.