[gelöst] Zeiger nicht benutzt...Freigeben?



  • Hallo,

    bisher habe ich Zeiger die ich definiert habe, auch immer benutzt und somit war klar: delete!

    Jetzt habe ich folgende Situation:

    class foo
      {
      private:
        database* db;
        benutzerverwaltung* benu;
      public:
        foo();
        ~foo();
      }
    
    foo::foo()
      {
      db=new database(..Datendaten...);
      if (db->ok())
        {
        benu=new benutzerverwaltung();
        }
      }
    
    foo::~foo()
      {
      delete db;
      delete benu; // ??????
      }
    

    Wenn jetzt keine Verbindung zur Datenbank zustande kommt, dann hab ich ja kein Object von benu. In meinem schlauen C++-Buch steht, man darf kein Object freigeben das nicht existiert, weil das verhalten undefiniert ist.

    Wie kann ich feststellen, ob mein Objects definiert wurde um festzustellen ob ich es löschen muss?

    Danke,
    Stefan



  • Was macht database im c'tor? Wirft er ne exception?

    Auf jeden Fall ist es sicher gut, wenn du den Zeiger mit 0 initialisierst, dann passiert bei dem delete sicher nichts falsches, wenn du keinen Zeiger bekommst. Im Falle einer exception, wird unter Umständen foo zerstört (je nachdem, wo du die exception fängst), was aber kein Problem ist, wenn du da dann delete 0 hast.

    EDIT:

    Wenn jetzt keine Verbindung zur Datenbank zustande kommt, dann hab ich ja kein Object von benu

    Wichtig ist hier, dass du unterscheidest, was nicht zustande gekommen ist. C++ ist es ziemlich egal, ob du jetzt eine Verbindung zur Datenbank hast, oder nicht. Somit kannst du durchaus ein gültiges C++ Objekt haben, auch wenn da drin die Verbindung nicht gültig ist. Dafür gibt es ja dann aber die exception, mit denen du auch C++ "mitteilen" kannst, dass da was so schief gelaufen ist, dass du nicht weisst was zu tun ist und die Verantwortung an den Aufrufer weiterleitest.
    Somit wird die Überprüfung auf ->ok() auch überflüssig, da du davon ausgehen kannst, dass, wenn new erfolgreich war und keine exception geworfen wurde das Objekt und deine Verbindung gültig ist.



  • Hi,

    du musst die Zeiger einfach vorher mit NULL (bzw. 0) initialisieren und dann später im Destruktor auf diesen Wert prüfen

    class foo
    {
    private:
    	database* db;
    	benutzerverwaltung* benu;
    public:
    	foo();
    	~foo();
    }
    
    foo::foo() :
    	db(NULL), benu(NULL)
    {
    	db=new database(..Datendaten...);
    	if (db->ok())
    	{
    		benu = new benutzerverwaltung();
    	}
    }
    
    foo::~foo()
    {
    	if (db != NULL)
    	{
    		delete db;
    	}
    	if (benu != NULL)
    	{
    		delete benu;
    	}
    }
    

  • Administrator

    hoagie23 schrieb:

    du musst die Zeiger einfach vorher mit NULL (bzw. 0) initialisieren und dann später im Destruktor auf diesen Wert prüfen

    Nein, prüfen musst du nicht. Ein delete auf einen Nullzeiger ist definiert und macht nichts. Also:

    int main()
    {
      int* i = new int(3);
      delete i;
      i = 0;
    
      delete i; // <- ist gültig!
    
      return 0;
    };
    

    Grüssli



  • db=new database(..Datendaten...);
    

    Im Konstruktor erzeugst du doch ein Objekt. Ob dein erzeugtes Objekt eine Verbindung zur Datenbank herstellen kann, ist davon unabhängig. Also auch, wenn du das Objekt, so wie du es beschreibst, nicht nutzt, ist es aber da, also muss es wieder zerstört werden.



  • Danke, das mit benu(NULL) klappt super. Das kann ich auch an anderen Stellen nehmen um zu prüfen ob das Object da ist.

    Was macht database im c'tor?

    c'tor? Bin noch starker Anfänger. Was ist das?



  • Er bezieht sich auch auf den Pointer auf ein Objekt vom Typ benutzerverwaltung das nur erstellt wird, wenn die Datenbankverbindung erfolgreich war.

    Ansonsten gilt was hier schon erwähnt wurde:
    - Pointer mit 0 initialisieren
    - Nach delete (außerhalb von Destruktor) Pointer auf 0 setzen



  • Ein delete auf einen Nullzeiger ist definiert und macht nichts.

    Also auch, wenn du das Objekt, so wie du es beschreibst, nicht nutzt, ist es aber da, also muss es wieder zerstört werden.

    Verstehe ich das richtig:

    Ein Object-Zeiger wie mein
    benutzerverwaltung* benu
    muss ich immer mit delete freigeben, auch wenn ich kein "new benutzerverwaltung" während der Laufzeit habe?



  • Sofern database nicht selbst eine Exception erzeugen kann, würde ich es so machen:

    //foo.h
    class foo
    {
    private:
        std::auto_ptr<database> db;
        std::auto_ptr<benutzerverwaltung> benu;
    public:
        foo();
        ~foo();
    }
    
    //foo.cpp
    #include "foo.h"
    
    namespace //anonymer namespace!
    {  
        std::auto_ptr<database> make_db(/*...*/)
        {
            std::auto_ptr<database> db(new database(/*...*/));
            if(!db || !db->ok())
            {
                throw database_connect_failed("blabla");
            }
            return db;
        }
    }
    
    foo::foo()
    : db(make_db(/*...*/))
    , benu(new benutzerverwaltung(/*...*/))
    { }
    
    foo::~foo()
    { 
        //hier muss nichts stehen
    }
    

    Im Falle eines Fehlers oder wenn ein foo-Objekt seinen Scope verliert, wird alles automatisch aufgeräumt.
    Ggf. nch den gleichen Mechanismus für benutzerverwaltung bauen.



  • stefanjann schrieb:

    muss ich immer mit delete freigeben, auch wenn ich kein "new benutzerverwaltung" während der Laufzeit habe?

    nein, musst du nicht. du darfst es aber, wenn der zeiger 0 ist.



  • c'tor = Konstruktor.

    Du musst new/delete immer in gleicher Zahl benutzen. Sprich, wenn du etwas mit new anforderst, dann musst du es auch wieder freigeben (mit delete).

    mach benu ( 0 ) draus und im d'tor ( = Destruktor ) machst du delete benu dann bist du auf der sicheren Seite.

    Wie gesagt mit exceptions ist das ganze ein wenig komplizierter. Die solltest du dir vlt. auch mal anschauen.



  • Außer einen (rohen) Pointer auf 0 zu setzen gibt es keine sinnige Methode um festzustellen ob dieser auf ein erzeugtes Objekt oder noch auf Datenmüll zeigt. Natürlich könnte man Statusvariablen mitschleifen oder ähnliches, es aber einfach auf 0 zu setzen ist einfacher und eleganter. Dann braucht man auch keine Abfragen sondern kann stumpf delete aufrufen.

    [smart/auto_ptr habe ich mal außen vor gelassen]



  • Excetions sind für mich auch noch recht neu und folglich der zweite Schritt. Im ersten Schritt wollte ich erst mal sauber meine Objecte anlegen und freigeben, bevor ich mir Gedanken machen, wann was schief gehen kann. Sind ja auch noch fast keine Funktionnen implementiert, kann also noch nicht viel passieren.
    Ich gehe nun folgenden Weg:

    foo::foo() : db(NULL), benu(NULL)
    

    und immer mit

    delete db;
    db = NULL;
    delete benu;
    benu = NULL;
    

    aufräumen.

    Danke für die vielen (echt schnellen) Hilfeposts.
    Stefan



  • Ja, du kannst das ruhig mit der Ruhe angehen, aber nur noch kurz:
    Das ganze wird bei dir nur gefährlich, wenn du das Objekt kopierst. 😉

    Kopier mal so ein Objekt und dann schau was passiert. Schau mit dem Debuger die Zeiger an und was genau passiert. (Kleiner Tipp: Etwas passiert einmal zu viel. ;))



  • Da ich database db an benutzerverwaltung beuv übergebe hätte ich beim Kopieren 2 Verbindungen offen. Aber mit Referenzen hab ich das umgangen. Ansonsten hab ich nichts gefunden was sich irgendwie falsch doppelt.



  • stefanjann schrieb:

    Da ich database db an benutzerverwaltung beuv übergebe hätte ich beim Kopieren 2 Verbindungen offen. Aber mit Referenzen hab ich das umgangen. Ansonsten hab ich nichts gefunden was sich irgendwie falsch doppelt.

    Naja. Nein. Wenn du foo kopierst:

    foo a (..);
    foo b (..);
    
    a = b;
    

    Dann wird da eben deine Datenbank nicht ein zweites mal aufgemacht, sondern lediglich alle Daten von b nach kopiert. wenn das Datenbankobjekt Kopiersemantik anbietet, dann ist das schonmal in Ordnung, aber spätestens beim zerstören von beuv läuft dann beim zweiten Objekt etwas schief, weil du den Speicher bereits freigegeben hast und ein zweites mal undefiniertes Verhalten hervorruft. (sprich meistens dein Programm abstürtzt..).



  • Achso,
    soweit hab ich nicht gedacht weil:

    Ich bin in wxWdigets und die Klasse ist mein BasisFrame. Die wird nicht kopiert und existiert immer nur einmal.
    Und von dortaus übergebe ich immer die db per Referenz an die Unterklassen und anderen Frames. Somit habe ich immer nur ein Object und das Erzeugen und löschen übernimmt immer der BasisFrame im c'tor und d'tor.
    (Hab schon was gelernt mit den Kürzeln)

    Aber danke für den Hinweis.
    Stefan



  • Wichtig ist es darauf zu achten wenn man mit Containern wie std::vector arbeitet und da drin Objekte (und keine Pointer) speichert. Denn der Container kopiert die immer gerne hin und her. Wenn eines deiner Objekte nun einen Pointer als Member hat, passiert in etwa folgendes:

    - Copy-Konstruktor für Objekt an neuer Stelle
    - Destruktor für Objekt an alter Stelle
    - Alle Pointer im neuen Objekt ungültig (bzw. evtl. im Konstruktor freigegeben)

    Da sollte man immer dran denken, wenn man plötzlich Probleme in dem Bereich hat, sich daran zu erinnern kann einem dann einige graue Haare sparen. 😉



  • Bei Datenbanken würde ich die Kopiersemantik lieber verbieten. 😉

    Du kannst Kopien und Zuweisungen verhindern, indem du Kopierkonstruktor und Zuweisungsoperator private deklarierst.

    Ansonsten, wenn man sequenzielle Container benutzt, aber keine Kopiersemantik bereitstellen will, wäre vielleicht Boost.Pointer Container etwas.


Anmelden zum Antworten