Fehler im Konstruktor -> sinnvolle Reaktion?



  • Dravere schrieb:

    ...
    Also erklär mir mal dieses "ihr" 🤡

    Grüssli

    Das waren Manfred T. und SCNR siehe

    Manfred T. schrieb:

    SCNR schrieb:

    Na so:

    class Test {
      public:
      Test(const char *name) {
        if(name == NULL)
          initialized = false;
        else
          initialized = true;
        this->name = name;
      }
    
      bool is_initialized() {
         return initialized;
      }
    
      private:
        boost initialized;
        std::string name;
    };
    

    Ist daran etwas schlecht? Ich mache das auch immer so.

    🙄 🤡 🤡



  • Dravere schrieb:

    SCNR -> Sorry Could Not Resist -> Troll.

    Danke für den Hinweis. Ist das Standard? 😉

    NAFAL -> Need a fucking acronym lexicon -> Stefan.



  • DStefan schrieb:

    Dravere schrieb:

    SCNR -> Sorry Could Not Resist -> Troll.

    Danke für den Hinweis. Ist das Standard? 😉

    NAFAL -> Need a fucking acronym lexicon -> Stefan.

    Ja, SCNR ist durchaus verbreitet.

    Mir ist die Sache mit den Exceptions aber leider noch nicht ganz klar geworden. In welcher Situation muss ich den reservierten Speicher selber freigeben und in welchen Situationen passiert das automatisch? Das alles im Bezug auf Exceptions im Konstruktor, die entweder durch Membervariablen oder eventuell sogar selber im betrachteten Konstruktor geworfen werden.


  • Administrator

    DStefan schrieb:

    Dravere schrieb:

    SCNR -> Sorry Could Not Resist -> Troll.

    Danke für den Hinweis. Ist das Standard? 😉

    NAFAL -> Need a fucking acronym lexicon -> Stefan.

    Hier:
    http://de.wikipedia.org/wiki/Liste_der_Abk%FCrzungen_(Netzjargon)
    😃

    asdasdasd schrieb:

    Mir ist die Sache mit den Exceptions aber leider noch nicht ganz klar geworden. In welcher Situation muss ich den reservierten Speicher selber freigeben und in welchen Situationen passiert das automatisch? Das alles im Bezug auf Exceptions im Konstruktor, die entweder durch Membervariablen oder eventuell sogar selber im betrachteten Konstruktor geworfen werden.

    Halte dich einfach an RAII (resource acquisition is initialization), dann geht es ohne Probleme. Zu RAII solltest du genügend im Netz finden.

    Grüssli



  • Dravere schrieb:

    asdasdasd schrieb:

    Mir ist die Sache mit den Exceptions aber leider noch nicht ganz klar geworden. In welcher Situation muss ich den reservierten Speicher selber freigeben und in welchen Situationen passiert das automatisch? Das alles im Bezug auf Exceptions im Konstruktor, die entweder durch Membervariablen oder eventuell sogar selber im betrachteten Konstruktor geworfen werden.

    Halte dich einfach an RAII (resource acquisition is initialization), dann geht es ohne Probleme. Zu RAII solltest du genügend im Netz finden.

    Grüssli

    Mir ist das Prinzip eigentlich klar, nur empfinde ich die Sache mit new als ein Spezialfall.

    In einigen Fällen brauche ich einfach einen Zeiger, da ich nicht extra eine Funktion init() schreiben will.

    Wenn ich aber folgendes habe:

    class MyClass
    {
     ClassA *a;
     ClassB *b;
     ClassC c;
    
     MyClass() : a(new ClassA)
     {
      try
      {
       b = new ClassB;
    
       if (!c.ready())
       {
        throw eMyClassNotReady();
       }
      }
      catch (...)
      {
       cleanup(); // Notwendig?
       throw;
      }
     }
    
     ~MyClass()
     {
      cleanup();
     }
    
     void cleanup()
     {
      delete a;
      delete b;
     }
    };
    

    Wie sieht es in diesem Fall mit Speicherfreigabe aus? Muss ich etwas freigeben?

    Ich sehe das momentan so:

    Für c wird der Destruktor automatisch aufgerufen, kein Problem
    Für a und b geschieht das jedoch nicht, da das Objekt nicht vollständig erzeugt werden konnte und daher der Destruktor nicht aufgerufen wird.

    Aber ich bin mir da eben nicht sicher und deswegen frage ich nun hier 🙂



  • Ja, cleanup ist hier notwendig. RAII macht schlussendlich auch nichts anderes, als das, allerdings einfach automatisch.


  • Administrator

    1. Du solltest b mit 0 initialisieren, damit es keine bösen Überraschungen gibt.
    2. Ja, das cleanup ist notwendig.
    3. Du verwendest hier aber nicht RAII! Mit RAII wäre nämlich ein cleanup gar nicht nötig.

    Grüssli



  • Dravere schrieb:

    1. Du solltest b mit 0 initialisieren, damit es keine bösen Überraschungen gibt.

    Stimmt, sehe ich natürlich ein 🙂

    Dravere schrieb:

    3. Du verwendest hier aber nicht RAII! Mit RAII wäre nämlich ein cleanup gar nicht nötig.

    Das heißt dann wohl ich muss auto_ptr nutzen 🙂



  • asdasdasd schrieb:

    Das heißt dann wohl ich muss auto_ptr nutzen 🙂

    Oder besser: die Smartpointer des TR1 oder von boost (scoped_ptr, shared_ptr, intrusive_ptr)...



  • asc schrieb:

    Oder besser: die Smartpointer des TR1 oder von boost (scoped_ptr, shared_ptr, intrusive_ptr)...

    Würde ich nicht verallgemeinern. Okay, als Member ist auto_ptr vielleicht nicht immer optimal, sonst hat er aber einige Anwendungsbereiche (als eines der wenigen Konstrukte, die im jetzigen Standard schon eine Art Move-Semantik bieten). shared_ptr wird meiner Meinung nach viel zu oft an Stellen eingesetzt, an denen man sich keine Gedanken um Besitzverhältnisse macht. Und intrusive_ptr braucht man eigentlich recht selten bis nie, wenns nur um RAII geht...

    Davon abgesehen bringen alle diese Smart Pointer nichts, wenn jedes Objekt seinen Speicherbereich selbst verwaltet und ein sinnvolles Kopieren möglich sein soll. Schliesslich möchte man nicht immer alles teilen. Dann ist man trotzdem wieder zu einer Alternative gezwungen (sei es jetzt manuelle Verwaltung durch die Grossen Drei, Container im Falle von mehreren Objekten oder ein spezieller Smart-Pointer mit Kopiersemantik).

    Aber falls die Klasse nicht kopierbar sein soll und man RAII und damit Exceptionsicherheit erreichen möchte, ist ein scoped_ptr erste Wahl.



  • asdasdasd schrieb:

    In einigen Fällen brauche ich einfach einen Zeiger, da ich nicht extra eine Funktion init() schreiben will.

    Was ist das denn für eine Begründung?

    asdasdasd schrieb:

    Wenn ich aber folgendes habe:

    class MyClass
    {
     ClassA *a;
     ClassB *b;
     ClassC c;
    
     MyClass() : a(new ClassA)
     {
      try
      {
       b = new ClassB;
       
       if (!c.ready())
       {
        throw eMyClassNotReady();
       }
      }
      catch (...)
      {
       cleanup(); // Notwendig?
       throw;
      }
     }
    
     ~MyClass()
     {
      cleanup();
     }
    
     void cleanup()
     {
      delete a;
      delete b;
     }
    };
    

    Oha! Wenn das Erzeugen/Initialisieren von *a schief läuft und dann cleanup aufgerufen wird, fürhst Du "delete b" aus, wobei b aber noch gar nicht initialisiert wurde -- noch nicht mal mit dem Nullzeiger.

    Warum nicht einfach ohne Zeiger? Ich sehe dem Beispiel nicht an, warum es notwendig sei, Zeiger zu speichern. Du machst den Code nur fehleranfällig damit. Beispielsweise hast Du die Dreierregel nicht befolgt. (!!)

    Muss das mit c.ready() sein? Kann nicht einfach der Konstruktor von C die Exception schmeißen?

    Dravere sagte: "Halte Dich an RAII". Das hast Du hier nur eher halbherzig befolgt. Idealerweise verwalten sich auch die Elemente selbst, soweit das geht. Das ist bequem und weniger fehleranfällig. Das geht natürlich nicht immer (wie soll man sonst std::vector implementieren?). Aber man sollte versuchen, die Zahl der explizit zu verwaltenden Resourcen pro Objekt auf 0 oder 1 zu reduzieren. Das kriegt man hin, in dem man den Verwaltungsaufwand in die "Objektelemente verlagert". Rohe Zeiger können das natürlich nicht. Bei Dir sind's zwei Zeiger. Gut, das ist noch vertretbar, aber es erfordert Vorsicht und ist fehleranfällig. Ich hätte es wahrscheinlich über auto_ptr gelöst:

    class MyClass {
      ...
    
      explicit MyClass(FactoryA & pfa, FactoryB & pfb)
      : a(0), b(0)
      {
        auto_ptr<ClassA> apa (pfa->make());
        auto_ptr<Classb> apb (pfb->make());
        a = apa.release();
        b = apb.release();
      }
    
      ~MyClass()
      {
        delete b;
        delete a;
      }
    
      MyClass(MyClass const& x)
      {
        // Dreierregel-Extra-Wurst!
      }
    
      MyClass& operator=(MyClass const& x)
      {
        // Dreierregel-Extra-Wurst!
      }
    
      ...
    };
    

    Das mit dem "FactoryA", "FactoryB" ist etwas, mit dem man die Speicherung von Zeigern motivieren kann. Hier muss MyClass nicht wirklich wissen, was das für Objekte genau sind, Hauptsache sie erben von ClassA und ClassB, welche hoffentlich einen virtuellen Destruktor haben.

    Beachte, wenn Du das A-Objekt und das B-Objekt direkt als Element der MyClasse benutzt hättest, bräuchtest Du auch keine "Extra-Wurst" bzgl Kopieren und Zuweisung. Warum? Die implizit vom Compiler definierten Operationen machen dann schon das Richtige.

    asdasdasd schrieb:

    Wie sieht es in diesem Fall mit Speicherfreigabe aus? Muss ich etwas freigeben?
    Ich sehe das momentan so:
    Für c wird der Destruktor automatisch aufgerufen, kein Problem
    Für a und b geschieht das jedoch nicht, da das Objekt nicht vollständig erzeugt werden konnte und daher der Destruktor nicht aufgerufen wird.

    Du setzt den Zeiger mit dem Objekt, auf das gezeigt wird gleich. Typischer Anfängerfehler. a und b werden sauber zerstört. Das worauf sie zeigen aber nicht.

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Oha! Wenn das Erzeugen/Initialisieren von *a schief läuft und dann cleanup aufgerufen wird, fürhst Du "delete b" aus, wobei b aber noch gar nicht initialisiert wurde -- noch nicht mal mit dem Nullzeiger.

    Das war'n Vertipper. Sollte heißen "...von b = new ClassB schief läuft..."


Anmelden zum Antworten