Problem mit try...except



  • Hmmm wie soll ich das ganze sonst machen?
    Ich muss ja mein DirectDraw Releasen. Und da ich ja ein Klassenkonzept schreiben wollte, um DirectX zu verwenden, hatte ich überlegt, die wichtigen Objekte in Klassen zu packen. Wenn ich jetzt aber extra Funktionen zum initialisieren und zu releasen der DirectX Objekte schreibe wäre es ja eigentlich schlechtes OOP, da zum initialisieren und zum aufräumen hinterher ja Konstruktor und Destruktor da sind.
    Weiterhin muss ich aber auch überprüfen, ob beim erstellen und später beim Release() alles korrekt abgelaufen ist (bin mir nicht sicher, ob nicht auch Speicherlöcher entstehen wenn man ein DirectX Objekt nicht am Ende releast). Und da zu einem guten Klassenkonzept ja auch Exceptions zur Fehlerbehandlung gehören, wollte ich Exceptions werfen wenn das FAILED Makro von DX mir true liefert. Wenn ich jetzt immer wieder retryen wollte, wenn das Makro mir true liefert könnte ich in eine Endlosschleife geraten. Und einfach ignorieren kann auch nicht das wahre sein, da DX Objekte eigentlich Released werden sollten.
    Irgendwie passt das alles nicht zusammen.
    Wie würdet ihr das ganze denn sonst lösen, sodass ich gutes OOP habe, Fehler von DX abfangen kann (am besten mit Exception) und das mit dem try...except auch alles passt?
    Hier noch der Code aus Konstruktor und Destruktor, damit man sich das besser vorstellen kann:

    CDraw7Main::CDraw7Main(HWND Hwindow,int Width,int Height, int Bpp, int flags):
    hwnd(Hwindow),width(Width),height(Height),bpp(Bpp)
    {
      if (FAILED(DirectDrawCreateEx(NULL,(void**)&lpdd7,IID_IDirectDraw7,NULL)))
        throw "Fehler bei Erstellung";
    
      if(FAILED(lpdd7->SetCooperativeLevel(hwnd, flags)))
        throw "Fehler bei Cooperation Level";
    
      if(FAILED(lpdd7->SetDisplayMode(width,height,bpp,0,0)))
        throw "Fehler bei Display Mode";
    }
    
    CDraw7Main::~CDraw7Main()
    {
      if (lpdd7)
      {
        if (FAILED(lpdd7->Release()))
          throw "Fehler bei Release";
        else
          lpdd7=NULL;
      }
    }
    

    PS: Hab noch nicht so viel Erfahrung mit OOP in C++, will aber nicht erst DX mit globalen Funktionen und Variablen und so machen, wie es in meinem DX Buch gemacht wird, sondern gleich nen ordentliches Klassenkonzept verwenden und damit auch mehr Erfahrungen mit C++ sammeln...



  • Amateur schrieb:

    Hmmm wie soll ich das ganze sonst machen? ...

    Simon2 schrieb:

    ...wenn man im Dtor Funktionen aufrufen muss, die exceptions werfen könnten, muss man die eben abfangen und entweder geeignet retry-en oder ignorieren...

    Gruß,

    Simon2.

    Dass Du nicht auf "init()/release()" baust, ist schonmal eine gute Idee, aber eben das Werfen von exceptions im Dtor eben nicht. Was immer Du in einer solchen Situation noch tun willst, solltest Du innerhalb des Dtors machen - eben auch meinetewegen einen entsprechenden Retry-Mechanismus (der ja trotzdem irgendwann aufgeben muss - egal, ob im Dtor oder sonstwo)....

    Kleine Anmerkung am Rande:

    Amateur schrieb:

    ..

    ...
    CDraw7Main::~CDraw7Main()
    {
    ...      lpdd7=NULL;
    

    Wenn lpdd7 eine Membervariable von CDraw7Main sein sollte, ist das "Nullsetzen" überflüssig - es hört sowieso auf zu existieren, da ist der Wert unwichtig.
    Aber es lässt mich eine andere Designschwäche vermuten: Wer kann lpdd7 denn sonst noch auf Null setzen (und vermutlich Release() aufrufen) ? Was wird da denn getan, wenn der Release() schiefgeht ? Ggf. solltest Du im Dtor auch einfach diese Funktion (wo dann auch der Retry implementiert ist) aufrufen (und eine evt. exception ignorieren).

    Falls lppd7 aber eine "externe Ressource" darstellt, ist nochmal gründlich zu untersuchen, wie die Lebenszeiten davon mit CDraw7Main zusammenhängen - so ganz unabhängige Teile schreien bisweilen nach einem ganz anderen Handling als "Memberattribute" (Ctor/Dtor) ...

    Gruß,

    Simon2.



  • Release() liefert schonmal garkeinen HRESULT zurück, daher ist FAILED(Release()) auch Blödsinn.
    COM bietet selbst schon keine Möglichkeit bei "Release()" irgendwelche Fehler zurückzumelden, d.h. es gibt garkeine "Fehlersituation" auf die du im dtor reagieren müsstest.

    Wenn du mal was anderes schreibst, wo dann im dtor vielleicht Funktionsaufrufe vorkommen können die wirklich Fehlercodes zurückliefern (CloseHandle z.B.), dann ist die Frage nicht so einfach zu beantworten. Schliesslich gibt der dtor das Objekt frei, es existiert danach nichtmehr. Alles was also als unmittelbare Reaktion auf den Fehler passieren soll muss demnach auch im dtor passieren. Eine halbwegs einfache Möglichkeit ist zuzusehen dass im dtor immer nur Code steht der "eigentlich immer funktionieren muss" (wie z.B. CloseHandle mit einem gültigen HANDLE), und im Fehlerfall einfach terminate() aufzurufen.

    Fehlerbehandlung in cleanup Code ist aber ein eigenes Thema, und leider nicht ganz trivial.



  • hustbaer schrieb:

    Release() liefert schonmal garkeinen HRESULT zurück,...

    Was
    a) ein guter Hinweis darauf ist, dass die Programmierer sich schonmal an diese "Aufräumen muss klappen"-Maxime gehalten haben (und man das bedenkenlos in seinem Dtor übernehmen kann) und
    b) ich natürlich nicht wusste (kenne COM einfach nicht).

    😉

    Gruß,

    Simon2.



  • Ok danke für die Antworten.
    Dass ich Release nicht mit FAILED überprüfen muss wusste ich nicht. Wird halt überall geschrieben alles mit FAILED zu überprüfen, von daher dachte ich auch dass Release das benötigt.
    @Simon2:
    lpdd7 ist eine private Membervariable. Sie stellt eigentlich so das Kernobjekt von der Klasse da. In meiner Klasse ruft bisher keine andere Funktion Release() auf oder setzt auf NULL bis auf den Destruktor. Ich hab das ganze auf NULL gesetzt, weil es ein Pointer ist und das in den Beispielen aus dem Buch auch immer so gemacht wird. Ich denke mal dass lpdd7 bei Release nicht auf NULL gesetzt wird und man das deshalb per Hand macht, damit der Pointer nicht irgendwohin zeigt. Aber keine Ahnung ob ich damit richtig liege...
    Ist aber auch nicht Thema dieses Threads.
    Deshalb nochmal danke an alle die mir weitergeholfen haben!!!



  • Amateur schrieb:

    ...
    lpdd7 ist eine private Membervariable. ...

    Damit kann nach Ablauf des Dtors niemand mehr auf sie zugreifen und ihr Wert ist vollkommen irrelevant (bzw. sowieso nicht mehr definiert). Dieses "Setzen auf NULL" ist einen beliebte Technik, um sich eine weitere Information zu merken, ohne ein weiteres Attribut einführen zu müssen: "Zeigt der Zeiger auf etwas Sinnvolles ?" ... brauchst Du aber hier nicht - und hast mich damit in die Irre geführt. 😉

    Ist aber wirklich eher eine Kleinigkeit, auf der ich hier nur rumreite, weil ich das Gefühl habe, dass Du mit dem ganzen Thema noch nicht ganz sicher bist und ich Dir dabei helfen möchte, "die richtige Denke" zu entwickeln.

    Gruß,

    Simon2.



  • Ok alles klar! Danke!
    Ja ich hab mich bei dem Design an den Code aus dem DirectX Buch gehalten und war mir nicht sicher ob das NULL setzen wichtig ist. Denn dort wurde es auch so gemacht und zwar in einer Funktion die erst nach der Message Schleife in der WinMain aufgerufen wird. Deshalb dachte ich, dass das NULL setzen einen Sinn hat, da in dem Beispiel danach auch nichts mehr mit lpdd7 passiert...



  • Das Nullsetzen hat dann einen Sinn wenn es die Variable danach noch gibt. Nachdem der dtor gelaufen ist gibt es sie aber nichtmehr. In deinem Buch wird das wahrscheinlich nicht im dtor stehen, daher wird dort auf 0 gesetzt.
    (BTW: NULL ist ein C-ismus, und hat in C++ Code IMHO nix verloren, schreib stattdessen einfach 0.)

    Zu Release(): AddRef() und Release() sind 2 Spezialfälle, die liefern nämlich nen ULONG zurück, der Wert ist der neue Reference-Count des Objektes. (Im Allgemeinen sollte man den Returnwert aber immer ignorieren, max. zum Debuggen "darf" man den noch verwenden. Alles andere ist "böse".) Eine COM Methode kann zwar grundsätzlich verschiedenste Dinge als Returnwert haben, aber bei allen anderen Funktionen hat es sich eingebürgert nen HRESULT zurückzuliefern, und bei "automation kompatiblen" Interfaces ist es AFAIK sogar vorgeschrieben.

    Für AddRef() und Release() wurde eine Ausnahme gemacht, die aus verschiedenen Gründen Sinn macht. Und die man sich auch "leisten kann", z.B. da beide keine Fehler zurückzuliefern können brauchen. Bzw. es wäre bei beiden Funktionen sogar ziemlich übel wenn sie Fehler zurückliefern könnten, denn dann müsste man an Stellen Error-Handling machen wo oft garkein vernünftiges Error-Handling möglich ist.



  • Ok...
    Auch wenns nicht ganz hier hin passt:
    Kann ich dann überall wo ich normalerweise NULL nehme (meistens bei Funktionen als Parameter oder so) 0 verwenden? Wozu gab/gibt es dann die Unterscheidung zwischen NULL und 0? Ich dachte das macht man um zwischen Pointern und dem normalen Wert 0 zu unterscheiden. Ist das in C++ nicht mehr der Fall oder wieso sollte man besser 0 verwenden?



  • Naja, die einzig "unproblematische" Variante NULL in C++ zu definieren ist

    #define NULL 0
    

    Warum soll ich dann NULL schreiben? Vor allem bietet es keine Sicherheit, ich kann ja in C++ dann genauso sowas schreiben:

    int x = NULL;
    

    Ich bin mir nichtmal sicher ob NULL offiziell zum C++ Standard gehört...


  • Mod

    hustbaer schrieb:

    Naja, die einzig "unproblematische" Variante NULL in C++ zu definieren ist

    #define NULL 0
    

    Warum soll ich dann NULL schreiben? Vor allem bietet es keine Sicherheit, ich kann ja in C++ dann genauso sowas schreiben:

    int x = NULL;
    

    Ich bin mir nichtmal sicher ob NULL offiziell zum C++ Standard gehört...

    NULL gehört schon dazu, vermittelt durch die C-Header. Ich halte das Argument auch nicht für ganz stichhaltig. Es gibt schließlich eine Menge Beispiele guter Programmierpraxis, deren Verletzung der Compiler nicht diagnostizieren kann oder darf. Die Verwendung von NULL (so wie wir es heute haben) ist eine Frage der Konvention und richtet sich an den Leser eines Programms, indem sie eine bestimmte Intention ausdrückt.



  • Außerdem kann man ab 2009 in seinen Programmen NULL automatisch durch nullptr ersetzen. Mit der Suchen&Ersetzen-Funktion eures Lieblingseditors. 😃 😉


Anmelden zum Antworten