Fehler Destruktor



  • Hey Leute,

    ich erhalte bei Verwendung eines Destruktors immer eine Fehlermeldung. Der Desktruktor löscht einfach nur
    ein dynamisch angelegte Objekt ptrArr. Die Klasse ist wie folg aufgebaut:

    class doubleArr
    {
            private:
    	     int* ptrArr;
    	     int len;
    
            public:
    	     doubleArr(int l) : len(l), ptrArr(nullptr)
    	     {
    		   ptrArr = new int[l];
    		   ptrArr[0] = 1;
    		   ptrArr[1] = 2;
    		   ptrArr[2] = 3;
    	     }
    
    	     ~doubleArr()
    	     {
    		   delete[] ptrArr;
    	     }
    
    int main()
    {
    	doubleArr test1(10);	
    	doubleArr test2(test1);
    
    	~test2(); //Fehler
    }
    

    Probehalber habe ich die Definition des Destruktor auch in einer seperaten Quelldatei ausgelagert. Trotzdem ist immer
    wieder der Fehler " error C2064: Ausdruck ergibt keine Funktion, die 0 Argumente übernimmt" erschienen. Was mache ich verkehrt? Vielen Dank!



  • Den Destruktor musst du nicht selbst aufrufen. Der Destruktor wird aufgerufen wenn dein Klasse aus dem Speicher gelöscht wird.

    https://de.wikipedia.org/wiki/Konstruktoren_und_Destruktoren



  • Es gibt in C++ nur einen Grund einen Destruktor explizit aufzurufen, wenn man Objekte die mit placement new konstruiert worden sind, dekonstruieren will. Sonst macht man das nie.



  • Und darum ist es auch gut so, daß @C-Sepp nicht weiß, wie man einen Destruktor korrekt aufruft. 🙂



  • Nachtrag: Einen Leak UB hat er gleich mit eingebaut. (Rule of five nicht beachtet!)

    Die Reihenfolge der Initialisierung stimmt nicht.

    Ich habe es einmal nachgebessert, damit Du sehen kannst, wie oft der von Dir geschriebene Konstruktor und Destruktor aufgerufen wird.

    #include <iostream>
    #include <stdexcept>
      
    class doubleArr {
        int* ptrArr;
        size_t len;
    public:
        doubleArr (const size_t l) : ptrArr (nullptr), len(l) {
            std::cerr << "doubleArr::doubleArr (const size_t)" << std::endl;
            if (l < 3) throw std::runtime_error ("array size to small");
    
            ptrArr = new int[l];
            ptrArr[0] = 1;
            ptrArr[1] = 2;
            ptrArr[2] = 3;
        }
    
        ~doubleArr () {
            delete[] ptrArr;
            std::cerr << "doubleArr::~doubleArr()" << std::endl;
        }
    };
    
    int main() {
        doubleArr test1(10);
        doubleArr test2(test1);
    }
    


  • Kein Leak, sondern UB beim Freigeben der Objekte (da test2 mittels des [automatisch vom Compiler bereitgestellten] Kopierkonstruktors erzeugt wurde)! Stichwort: The rule of three/five/zero



  • Es war mir schon klar, dass dynamisch auf dem Heap angelegte Obejkte mit Programmende automatisch wieder gelöscht werden. Ich wollte trotzdem mit delete den Speicherplatz mal explizit schon vorher löschen und dann mit diversen
    Zugriffsmethoden schauen, was dann im Speicher drinnen steht.
    Der Desktruktoraufruf müsste doch stimmer. Erst die Tilde, dann der Objektname und schließlich die Klammern?



  • In Zusammenhang mit MemoryLeaks habe ich gerade auch gelesen, dass der mit new auf auf dem Heap angelegte Speicher
    immer mit delete freigegeben werden sollte.



  • @C-Sepp sagte in Fehler Destruktor:

    Es war mir schon klar, dass dynamisch auf dem Heap angelegte Obejkte mit Programmende automatisch wieder gelöscht werden. Ich wollte trotzdem mit delete den Speicherplatz mal explizit schon vorher löschen

    Nein, nicht beim Programmende. Die werden gelöscht, wenn
    a) du delete aufrufst (natürlich nur bei Objekten, die du mit new angelegt hast)
    b) wenn das Objekt out-of-scope geht, das heißt, mit Passieren der geschwungenen Klammer zu } des Blocks, wo die Variable angelegt wurde.

    b) ist der Normalfall.

    int main() {
        {
            Objekt o;
        } // hier wird ~o automatisch aufgerufen
        std::cout << "Das Objekt o wurde gelöscht.\n";
    }
    

    In Zusammenhang mit MemoryLeaks habe ich gerade auch gelesen, dass der mit new auf auf dem Heap angelegte Speicher immer mit delete freigegeben werden sollte.

    Richtig, aber es ist besser, überhaupt nicht selbst new/delete aufzurufen, sondern Container-Klassen wie std::vector oder Smartpointer-Klassen wie unique_ptr zu nutzen.



  • Alles klar...danke für die schnelle Antwort,
    Stimmt....das hatte ich auch schon gelesen.
    Aber warum erhalte ich diese Fehlermeldung?



  • Weil du versuchst, etwas von Hand zu machen, das danach automatisch passiert. Lösche also die Zeile mit dem Destruktor.

    Der Aufruf wäre syntaktisch korrekt test2->~doubleArr(), aber wie schon versucht zu sagen, es wäre falsch, das so zu benutzen. Dann ist noch der Fehler mit dem Copy-Constructor drin, der aber ja schon angemerkt wurde.



  • @Th69
    Ja, natürlich hast Du Recht. In der Eile die falsche Formulierung genutzt.

    @C-Sepp sagte in Fehler Destruktor:

    Ich wollte trotzdem mit delete den Speicherplatz mal explizit schon vorher löschen und dann mit diversen
    Zugriffsmethoden schauen, was dann im Speicher drinnen steht.

    Nein, der Sinn einen eigenen korrekt geschriebenen Klasse ist es ja gerade, dass man die Resourcen eben nicht von Hand freigibt!
    Dieser Link hier ist sinnvoll durchzulesen https://en.cppreference.com/w/cpp/language/rule_of_three



  • @C-Sepp sagte in Fehler Destruktor:

    Erst die Tilde, dann der Objektname und schließlich die Klammern?

    Nicht der Objektname, der Klassenname.
    Einen Destruktor ruft man auf wie jede andere Memberfunktion. Wenn du nur mal aus Interesse schauen willst was nach der Ausführung des Destruktors in dem Speicherbereich des ehemaligen Objekts steht, das kannst du einfach so machen:

    T* t = new T();
    t->~T(); // Destruktor aufrufen ohne Speicher freizugeben
    // <hier im Debugger anhalten um nachzusehen>
    operator delete(t); // Speicher freigeben ohne Destruktor aufzurufen
    

    Ich fürchte bloss dass dich das Ergebnis nur noch mehr verwirren wird.



  • Was ist daran so verwirrend. Zum Zugriff auf die Datenelemente der Klasse habe ich mir entsprechende Zugriffmethoden
    definiert und mit geschaut, was die so liefern. getlen, getptr und den Indexoperator:

    ...
    int getlen(void) const
    {
    	return len;
    }
    
    int* getptr(void) const
    {
    	return ptrArr;
    }
    
    int operator[](int index) const
    {
    	return ptrArr[index];
    }
    

    Wie erwartet wurden mit test2.~doubleArr() bzw. ptrdoubleArr ->~doubleArr() die Wert des dynmaisch adressierten
    Speicherbereichs gelöscht. Vielen Dank!



  • @C-Sepp sagte in Fehler Destruktor:

    Was ist daran so verwirrend.

    ...

    Wie erwartet wurden mit test2.~doubleArr() bzw. ptrdoubleArr ->~doubleArr() die Wert des dynmaisch adressierten
    Speicherbereichs gelöscht. Vielen Dank!

    Naja genau das. Weil das halt so nicht stimmt. Probier das ganze mal mit nem etwas grösseren Array, zig~hunderte Einträge. Und dann guck dir alle Einträge an. Was da nachher drinsteht ist schlicht undefiniert, und i.A. wird bei grösseren Arrays genug von den Werten übrig bleiben. Du kannst nicht davon ausgehen dass da was "gelöscht" oder überschrieben wird.



  • @C-Sepp sagte in Fehler Destruktor:

    Wie erwartet wurden mit test2.~doubleArr() bzw. ptrdoubleArr ->~doubleArr() die Wert des dynmaisch adressierten
    Speicherbereichs gelöscht. Vielen Dank!

    NEIN! DAS SOLLST DU SO NICHT AUFRUFEN!!

    Laut und verständlich genug?



  • @C-Sepp sagte in Fehler Destruktor:

    Was ist daran so verwirrend. Zum Zugriff auf die Datenelemente der Klasse habe ich mir entsprechende Zugriffmethoden
    definiert und mit geschaut, was die so liefern. getlen, getptr und den Indexoperator:

    int operator[](int index) const
    {
    	return ptrArr[index];
    }
    

    So sollte man den Indexoperator auch nicht definieren.

    T& operator[] (const size_t i);
    

    So kann man Elemente auf auch korrekt zugreifen und verändern.



  • Laut und verständlich genug....new und delete sollte man besser erst gar nicht benutzen.



  • Das außerdem (da es Smartpointer, std::vector, etc. gibt), aber hier ging es darum, daß du den Destruktor nicht selbst im Code aufrufen sollst!



  • @C-Sepp sagte in Fehler Destruktor:

    Laut und verständlich genug....new und delete sollte man besser erst gar nicht benutzen.

    Jein, es gibt wenig Gründe dafür. Aber wenn man es tut, dann sollte man es auch richtig machen. Wenn Du das für Übungszwecke umsetzen willst, kannst Du das hier einmal durch exerzieren. Dann siehst Du auch, weshalb man das normalerweise nicht tun sollte.

    Placement new und delete nutzt man nur wenn man selbst Container entwirft. Das ist sicherlich interessant, aber für Anfänger zuerst unwichtig. Denn Container Design ist nochmals eine ganze Ecke schwerer.


Anmelden zum Antworten