JLI Spieleprogrammierung Foren-Übersicht JLI Spieleprogrammierung

 
 FAQFAQ   SuchenSuchen   MitgliederlisteMitgliederliste   BenutzergruppenBenutzergruppen 
 medals.php?sid=88728fedfe41ff33d51beff85f89aa2fMedaillen   RegistrierenRegistrieren   ProfilProfil   Einloggen, um private Nachrichten zu lesenEinloggen, um private Nachrichten zu lesen   LoginLogin 

Resourcenverwaltung - oder keine Speicherlecks mehr

 
Neues Thema eröffnen   Neue Antwort erstellen    JLI Spieleprogrammierung Foren-Übersicht -> Tutorials
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen  
Autor Nachricht
AFE-GmdG
JLI MVP
JLI MVP


Alter: 39
Anmeldedatum: 19.07.2002
Beiträge: 1374
Wohnort: Irgendwo im Universum...
Medaillen: Keine

BeitragVerfasst am: 25.02.2009, 00:33    Titel: Resourcenverwaltung - oder keine Speicherlecks mehr Antworten mit Zitat

Resourcenverwaltung - oder keine Speicherlecks mehr

Resourcen in C++ sind alle "Dinge", die nach gebrauch wieder freigegeben werden müssen.
Auch Objekte, die einen internen Status besitzen, welcher am Ende unbedingt wieder zurückgesetzt oder freigegeben werden muss kann man zu Resourcen zählen. Zweiteres sind z.B. Schriftobjekte von Windows (HFONTs) genaueres dazu weiter unten.

Der Typ std::auto_ptr<T>

Ein std::auto_ptr<T> ist ein so genannter intelligenter Zeiger. Das bedeutet, dass er uns - richtig angewendet - das Leben so richtig einfach machen kann:
Wir nutzen dabei den Umstand aus, dass eine lokale Variable bei verlassen des Gültigkeitsbereiches automatisch freigegeben wird:
CPP:
class MyClass()
{

public:
  // ctor
  MyClass()
  {
  }

  // dtor
  ~MyCalss()
  {
  }

  // Variable;
  int i;

};

void MyFunc()
{
  // Achtung: Ich verwende hier kein new()
  MyClass lokaleVariable;

  // Die Variable kann ganz normal verwendet werden.
  lokaleVariable.i = 42;
} // Der Gültigkeitsbereich der Funktion ist beendet - der dtor wird hier automatisch aufgerufen.


Da dieses Beispiel noch nicht sehr produktiv war - und da noch kein Speicher dynamisch allokiert werden musste, gehen wir jetzt zum nächsten Schritt: wir wollen diesmal die Klasse mit new allokieren, weil wir z.B. vorhaben, den Pointer an andere Funktionen weiterzureichen:

CPP:
void MyFunc()
{
  MyClass* pMyClass=new MyClass();
  // Weiterer Code
  pMyClass->i = 42;
} // Huch, wir haben vergessen, pMyClass wieder freizugeben - Speicherleck...


Hier kommt jetzt der auto_ptr ins Spiel:
Der abgeänderte Code sieht jetzt folgendermassen aus:
CPP:
void MyFunc()
{
  std::auto_ptr<MyClass> pMyClass(new MyClass());
  // Es wurde jetzt ein Intelligenter Zeiger angelegt.
  // Weiterer Code
  pMyClass->i = 42; // Durch OperatorÜberladung von op -> Funktioniert dies weiterhin.
} // der auto_ptr sorgt jetzt bei verlassen der Funktion, dass delete für MyClass aufgerufen wird.


Ein Resourcen-Beispiel

Die folgende Klasse stellt eine Resource dar, welche für Win32-GDI-Objekte verwendet werden kann - wie z.B. Schriftarten:

GdiResource.h
CPP:
#pragma once

#include <Windows.h>
#include <memory>

class GdiResource
{

   // Variablen
private:
   HGDIOBJ _hGdiObj;

   // ctor
public:
   explicit GdiResource(HGDIOBJ hGdiObj);
private:
   GdiResource(const GdiResource&); // Wird nicht implementiert
   // ZuweisungsOp - ebenfalls privat.
   const GdiResource& operator=(const GdiResource&); // Wird nicht implementiert

   // dtor
public:
   ~GdiResource();

   // Operatoren
public:
   const HGDIOBJ operator*() const;

};

Es wird dafür gesorgt, dass eine Resource dieser Art nicht Kopiert oder zugewiesen werden kann. Der Kopier-Konstruktor und der ZuweisungsOperator sind privat Definiert und werden nicht Implementiert.

Hier die Implementation:
GdiResource.cpp
CPP:
#include "GdiResource.h"

GdiResource::GdiResource(HGDIOBJ hGdiObj) :
   _hGdiObj(hGdiObj)
{
}

GdiResource::~GdiResource()
{
   if(_hGdiObj)
      DeleteObject(_hGdiObj);
}

const HGDIOBJ GdiResource::operator *() const
{
   return _hGdiObj;
}

Diese Klasse kapselt nun alle Win32-Resourcen, welche ein HGDIOBJ sind - wie z.B. ein HFONT.
Mit operator *() kann man später auf das Objekt selbst zugreifen. Wie das geht zeige ich gleich. Die Verwendung dieser Klasse ist nun folgendermassen:

CPP:
std::auto_ptr<GdiResource> CreateMyFont(int em, bool variableWidth, bool bold, bool italic, bool underline)
{
   return std::auto_ptr<GdiResource>(
      new GdiResource(::CreateFont(-MulDiv(em, GetDeviceCaps(_hDC, LOGPIXELSY), 72),
      0, 0, 0, bold?FW_BOLD:FW_NORMAL, italic?TRUE:FALSE, underline?TRUE:FALSE,
      FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
      (variableWidth?VARIABLE_PITCH:FIXED_PITCH)|FF_SWISS, NULL)));
}

void MyFunc(HDC hDC)
{
   std::auto_ptr<GdiResource> pMyFont=CreateMyFont(12, true, false, false, false);

   // Selektiere den Font in den aktuellen DeviceContext:
   SelectObject(hDC, **pMyFont);
} // Fertig.


Das **pMyFont gibt das HFONT (eigendlich das HGDIOBJ) zurück.
pMyFont ist der intelligente Zeiger
*pMyFont ist die GdiResource und
**pMyFont ist das Font selbst. Dafür war die Überschreibung des *-Operators weiter oben nötig.

Doch Halt! Da fehlt doch noch was!

Wer sich die API-Funktion SelectObject() anschaut, der stellt fest, dass die Funktion "das alte GDI-Objekt zurückgibt" - und was viel wichtiger ist, dass bei verlassen der Zeichenreoutine (EndPaint()) das alte FontObjekt wieder selektiert sein muss.
Auch dafür eignet sich eine ResourcenKlasse - und zwar für den DeviceContext.

Die 2. Klasse ist etwas umfangreicher, weil ich sie in dieser Form bereits verwende.
Sie kapselt das Win32-DeviceContext-Objekt HDC.
Ich zeige hier beispielhaft, wie mit der Klasse Schriftarten innerhalb des DCs verwaltet werden. gleiches kann man für Cursor, Bitmaps, Brushes, Pens usw. hinzufügen...

DeviceContextResource.h
CPP:
#pragma once

#include <Windows.h>
#include <memory>
#include "GdiResource.h"

class DeviceContextResource
{

   // Variablen
private:
   HWND _hWnd;
   HDC _hDC;
   bool _useEndPaint;
   PAINTSTRUCT ps;
   HFONT _oldFont;

   // ctors - alle privat.
private:
   explicit DeviceContextResource(HWND, bool);
   DeviceContextResource(const DeviceContextResource&); // Wird nicht implementiert
   // ZuweisungsOp - ebenfalls privat.
   const DeviceContextResource& operator=(const DeviceContextResource&); // Wird nicht implementiert

   // dtor
public:
   ~DeviceContextResource();

   // Fabrik-Funktion
public:
   static std::auto_ptr<DeviceContextResource> BeginPaint(HWND);
   static std::auto_ptr<DeviceContextResource> GetWindowDC(HWND);

   // Operatoren
public:
   const HDC operator*() const;

   // Funktionen
public:
   std::auto_ptr<GdiResource> CreateMyFont(int em=10, bool variableWidth=false, bool bold=false, bool italic=false, bool underline=false);
   void UseMyFont(HFONT font);

};


Selbst der ctor dieser Klasse ist nun Privat. Dafür gibt es 2 Fabrikfunktionen, welche die beiden verschiedenen Arten wiederspiegeln, wie man an ein DeviceContext kommt. Entweder mit BeginPaint innerhalb der Nachrichtenbehandlungsroutine WM_PAINT oder mit GetWindowDC zu einem beliebigen Zeitpunkt.
Es gibt noch weitere Möglichkeiten, aber für den Anfang sollen diese beiden ausreichen.

Hier jetzt die Implementierung davon:
DeviceContextResource.cpp
CPP:
#include "DeviceContextResource.h"

// ctor
DeviceContextResource::DeviceContextResource(HWND hWnd, bool useEndPaint)
   : _hWnd(hWnd), _hDC(NULL), _useEndPaint(useEndPaint), ps(), _oldFont(NULL)
{
   if(_useEndPaint) {
      _hDC=::BeginPaint(_hWnd, &ps);
   } else {
      _hDC=::GetWindowDC(_hWnd);
   }
}

// dtor
DeviceContextResource::~DeviceContextResource()
{
   if(!_hDC)
      return;
   if(_oldFont)
      SelectObject(_hDC, _oldFont);
   if(_useEndPaint) {
      ::EndPaint(_hWnd, &ps);
   } else {
      ::ReleaseDC(_hWnd, _hDC);
   }
}

std::auto_ptr<DeviceContextResource> DeviceContextResource::BeginPaint(HWND hWnd)
{
   return std::auto_ptr<DeviceContextResource>(new DeviceContextResource(hWnd, true));
}

std::auto_ptr<DeviceContextResource> DeviceContextResource::GetWindowDC(HWND hWnd=NULL)
{
   return std::auto_ptr<DeviceContextResource>(new DeviceContextResource(hWnd, false));
}

const HDC DeviceContextResource::operator *() const
{
   return _hDC;
}

std::auto_ptr<GdiResource> DeviceContextResource::CreateMyFont(int em, bool variableWidth, bool bold, bool italic, bool underline)
{
   return std::auto_ptr<GdiResource>(
      new GdiResource(::CreateFont(-MulDiv(em, GetDeviceCaps(_hDC, LOGPIXELSY), 72),
      0, 0, 0, bold?FW_BOLD:FW_NORMAL, italic?TRUE:FALSE, underline?TRUE:FALSE,
      FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
      (variableWidth?VARIABLE_PITCH:FIXED_PITCH)|FF_SWISS, NULL)));
}

void DeviceContextResource::UseMyFont(HFONT font)
{
   if(!_oldFont) {
      _oldFont=static_cast<HFONT>(SelectObject(_hDC, font));
   } else {
      SelectObject(_hDC, font);
   }
}


Fehlt nur noch die Verwendung

Dazu folgendes Testprogramm:

Program.cpp
CPP:
#include <Windows.h>
#include <memory>
#include <sstream>
#include <string>
#include "DeviceContextResource.h"
#include "GdiResource.h"

class Program
{

   // Variablen
private:
   HINSTANCE _hInstance;
   HWND _hWnd;
   std::auto_ptr<GdiResource> _font;

   // ctor, dtor
public:
   Program(HINSTANCE hInstance);
   virtual ~Program();

   // privaten Copyctor und privaten ZuweisungsOperator definieren aber nicht implementieren!
private:
   Program(const Program&);
   const Program& operator=(const Program&);

   // Funktionen
public:
   void InitializeClass();
   int MainLoop();
   static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
   virtual LRESULT OnClose();
   virtual LRESULT OnDestroy();
   virtual LRESULT OnPaint();

};

// Implementation

// ctor, dtor
Program::Program(HINSTANCE hInstance) :
   _hInstance(hInstance), _hWnd(NULL), _font()
{
   InitializeClass();
   std::auto_ptr<DeviceContextResource> dc(DeviceContextResource::GetWindowDC(_hWnd));
   _font=dc->CreateMyFont();
}

Program::~Program()
{
   // _font muss nicht explizit freigegeben werden, dass passiert automatisch.
}

// Funktionen
void Program::InitializeClass()
{
   WNDCLASS wc;
   wc.style         = 0;
   wc.lpfnWndProc   = (WNDPROC)WndProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = _hInstance;
   wc.hIcon         = 0;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = L"Program";

   RegisterClass(&wc);
}

int Program::MainLoop()
{
   MSG msg={0};
   int ret;

   _hWnd=CreateWindow(L"Program", L"Program",
      WS_VISIBLE|WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
      240, 80, NULL, NULL, _hInstance, (LPVOID)this);
   if(_hWnd==NULL)
      return -1;

   while(ret=GetMessage(&msg, NULL, 0, 0)) {
      if(ret==-1) {
         break;
      }
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return ret;
}

LRESULT CALLBACK Program::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   Program* pProgram=(Program*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
   switch(message)
   {
   case WM_CREATE:
      pProgram=*(Program**)lParam;
      SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pProgram);
      return 0;
   case WM_PAINT:
      return pProgram->OnPaint();
   case WM_SIZE:
      InvalidateRect(hWnd, NULL, TRUE);
      return 0;
   case WM_CLOSE:
      return pProgram->OnClose();
   case WM_DESTROY:
      return pProgram->OnDestroy();
   }
   return DefWindowProc(hWnd, message, wParam, lParam);
}

LRESULT Program::OnClose()
{
   DestroyWindow(_hWnd);
   return 0;
}

LRESULT Program::OnDestroy()
{
   PostQuitMessage(0);
   return 0;
}

LRESULT Program::OnPaint()
{
   std::auto_ptr<DeviceContextResource> dc(DeviceContextResource::BeginPaint(_hWnd));
   RECT rcBounds;
   GetClientRect(_hWnd, &rcBounds);
   SetTextColor(**dc, RGB(0x00, 0x00, 0x00));
   SetBkMode(**dc, TRANSPARENT);

   MoveToEx(**dc, 0, 20, NULL);
   LineTo(**dc, rcBounds.right, 20);
   MoveToEx(**dc, 5, 17, NULL);
   LineTo(**dc, 0, 20);
   MoveToEx(**dc, 5, 23, NULL);
   LineTo(**dc, 0, 20);
   MoveToEx(**dc, rcBounds.right-6, 17, NULL);
   LineTo(**dc, rcBounds.right-1, 20);
   MoveToEx(**dc, rcBounds.right-6, 23, NULL);
   LineTo(**dc, rcBounds.right-1, 20);

   MoveToEx(**dc, rcBounds.right-21, 0, NULL);
   LineTo(**dc, rcBounds.right-21, rcBounds.bottom);
   MoveToEx(**dc, rcBounds.right-24, 5, NULL);
   LineTo(**dc, rcBounds.right-21, 0);
   MoveToEx(**dc, rcBounds.right-18, 5, NULL);
   LineTo(**dc, rcBounds.right-21, 0);
   MoveToEx(**dc, rcBounds.right-24, rcBounds.bottom-6, NULL);
   LineTo(**dc, rcBounds.right-21, rcBounds.bottom-1);
   MoveToEx(**dc, rcBounds.right-18, rcBounds.bottom-6, NULL);
   LineTo(**dc, rcBounds.right-21, rcBounds.bottom-1);

   dc->UseMyFont(static_cast<HFONT>(**_font));

   std::wostringstream buffer;
   buffer << rcBounds.right;
   std::wstring str(buffer.str());
   SIZE sz;
   GetTextExtentPoint32(**dc, str.c_str(), static_cast<int>(str.length()), &sz);
   LONG px=(rcBounds.right-sz.cx)>>1;
   RECT rc={px, 20, px+sz.cx, 20+sz.cy};
   DrawText(**dc, str.c_str(), static_cast<int>(str.length()), &rc, DT_SINGLELINE|DT_NOPREFIX|DT_LEFT|DT_TOP);

   buffer.str(L"");
   buffer << rcBounds.bottom;
   str=buffer.str();
   GetTextExtentPoint32(**dc, str.c_str(), static_cast<int>(str.length()), &sz);
   px=rcBounds.right-21-sz.cx;
   LONG py=(rcBounds.bottom-sz.cy)>>1;
   rc.left=px;
   rc.top=py;
   rc.right=px+sz.cx;
   rc.bottom=py+sz.cy;
   DrawText(**dc, str.c_str(), static_cast<int>(str.length()), &rc, DT_SINGLELINE|DT_NOPREFIX|DT_LEFT|DT_TOP);

   return 0;
}

// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
   // Selbst hier kann man noch den auto_ptr verwenden...
   std::auto_ptr<Program> prg(new Program(hInstance));
   int ret=prg->MainLoop();
   return ret;
}


Das Gesamte Programm ist Speicherleckfrei, aufgrund der Nutzung von Resourcen-Kapselungs-Klassen und dem auto_ptr.

das Programm ist nebenbei für UNICODE ausgelegt und läuft sowohl im 32 als auch im 64-Bit-Betrieb

Fragen / Anregunden / Meinungen

Jetzt seid Ihr dran!

---
[Edit]Rechtschreibung verbessert.[/Edit]
[Edit]Private zu den ctors in GdiResource hinzugefügt - (hatte ich vergessen)[/Edit]
[Edit]Die Operatorfunktionen sollten Konstante Handles zurückgeben - sonst könnte man den internen Zustand ändern. const zu den Rückgabewerten hinzugefügt.[/Edit]
_________________
CPP:
float o=0.075,h=1.5,T,r,O,l,I;int _,L=80,s=3200;main(){for(;s%L||
(h-=o,T= -2),s;4 -(r=O*O)<(l=I*I)|++ _==L&&write(1,(--s%L?_<(L)?--_
%6:6:7)+\"World! \\n\",1)&&(O=I=l=_=r=0,T+=o /2))O=I*2*O+h,I=l+T-r;}


Zuletzt bearbeitet von AFE-GmdG am 26.02.2009, 17:27, insgesamt 4-mal bearbeitet
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden Website dieses Benutzers besuchen
Jonathan_Klein
Living Legend


Alter: 31
Anmeldedatum: 17.02.2003
Beiträge: 3430
Wohnort: Siegerland
Medaillen: Keine

BeitragVerfasst am: 25.02.2009, 02:25    Titel: Antworten mit Zitat

Ich würde noch sher die boost smartpointer empfehlen. Die gibt es für mehrere Einsatzgebiete, z.B. den shared_ptr, den man beliebig kopieren kann und der seinen Speicher erst freigibt, wenn die letzte seiner Kopie gelöscht wurde. Das ist auch praktisch für STL Container, vor allen Dingen,weil die nicht mit std::auto_ptr arbeiten können.
_________________
http://www.C3-Soft.de
http://www.nikodemus.net
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden Website dieses Benutzers besuchen
AFE-GmdG
JLI MVP
JLI MVP


Alter: 39
Anmeldedatum: 19.07.2002
Beiträge: 1374
Wohnort: Irgendwo im Universum...
Medaillen: Keine

BeitragVerfasst am: 25.02.2009, 10:14    Titel: Antworten mit Zitat

Da hast du vollkommen Recht.

Der std::tr1::shared_ptr<T> - oder Nachtrag zu std::auto_ptr<T>

Wie Jonathan_Klein bereits anmerkte kann man statt dem std::auto_ptr<T> natürlich auch den std::tr1::shared_ptr<T> verwenden.

Der AutoPointer hat die Eigenschaft, dass das Objekt, welches es verwaltet immer nur genau einmal existieren kann. Kopiert man ein AutoPointer auf einen anderen AutoPointer oder weist man dem neuem Autopointer den alten zu (=) übergibt der alte AutoPointer den Besitz des Objektes an den neuen. Der alte Pointer enthält danach nur nur noch einen NULL-Pointer.

Der SharedPointer implementiert das ganze mit einem ReferenzCounter. Dass heisst, er kann durch Kopieren oder Zuweisen echt Kopiert werden. Erst wenn der letzte dieser SharedPointer die Resource wieder abgibt, wird das enthaltene Objekt wieder freigegeben.

Ich bevorzuge in meinen Szenarien den AutoPointer. Falls aber Listen oder Vektoren von Resourcen verwaltet werden sollen, muss man auf den SharedPointer zurückgreifen, weil man aufgrund der Besitzübergabe einen AutoPointer nicht in einer Liste speichern kann (Bei der Zuweisung an eine Liste würde das Ursprüngliche Objekt an die Liste übergeben und nicht nur Kopiert werden)

Nachtrag zum Nachtrag

Um den SharedPointer verwenden zu können muss das Visual Studio ServicePack 1 installiert sein. Vorher kennt die Umgebung keinen TR1-Namespace.
_________________
CPP:
float o=0.075,h=1.5,T,r,O,l,I;int _,L=80,s=3200;main(){for(;s%L||
(h-=o,T= -2),s;4 -(r=O*O)<(l=I*I)|++ _==L&&write(1,(--s%L?_<(L)?--_
%6:6:7)+\"World! \\n\",1)&&(O=I=l=_=r=0,T+=o /2))O=I*2*O+h,I=l+T-r;}
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden Website dieses Benutzers besuchen
Beiträge der letzten Zeit anzeigen:   
Neues Thema eröffnen   Neue Antwort erstellen    JLI Spieleprogrammierung Foren-Übersicht -> Tutorials Alle Zeiten sind GMT
Seite 1 von 1

 
Gehe zu:  
Du kannst keine Beiträge in dieses Forum schreiben.
Du kannst auf Beiträge in diesem Forum nicht antworten.
Du kannst deine Beiträge in diesem Forum nicht bearbeiten.
Du kannst deine Beiträge in diesem Forum nicht löschen.
Du kannst an Umfragen in diesem Forum nicht mitmachen.


Powered by phpBB © 2001, 2005 phpBB Group
Deutsche Übersetzung von phpBB.de

Impressum