PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Spannende Frage über C++ Destruktoren



farell
14-11-2002, 16:32
Hi,

ich fange gerade an mich in die C++ Programmierung einzuarbeiten.
Da wollte ich mal ein Objekt als Parameter an eine Funktion übergeben, um zu sehenob und wie die Konstruktoren und Destruktoren aufgerufen werden.

Da ja eine Kopie des Objektes übergeben wird glaubte ich das es kein Konstruktor in der Funktion geben wird. Was sich hinterher auch als richtig herausstellte (wäre ja sonst auch sinnlos). Ich vermutete weiter das am ende der Klasse der Destruktor der Kopie aufgerufen wird. Und hier ist die komische entdeckung:
als die Funktion beendet wird, so werden aus irgenteinem Grund ZWEI Destruktoren aufgerufen?????

Beim weiteren Testen ist mir weiterhin aufgefallen das das nur der Fall ist wenn ich mit dem GNU Compiler unter Linux programmiere. Unter Win mit VC++ wird nur ein Destruktor aufgerufen.

Hat jemand zufällig eine erklärung für dieses Phänomen?

anda_skoa
14-11-2002, 17:42
Original geschrieben von farell
Hi,

ich fange gerade an mich in die C++ Programmierung einzuarbeiten.
Da wollte ich mal ein Objekt als Parameter an eine Funktion übergeben, um zu sehenob und wie die Konstruktoren und Destruktoren aufgerufen werden.

Da ja eine Kopie des Objektes übergeben wird glaubte ich das es kein Konstruktor in der Funktion geben wird. Was sich hinterher auch als richtig herausstellte (wäre ja sonst auch sinnlos).


Das ist nicht korrekt.
Es wird der Copy Contructor der Klasse aufgerufen.
Die Defaultimplementation macht eine binäre Kopie.
Wenn du im Objekt einen Pointer hast, der im Destructor freigegeben wird, hast du ein ziemlich übles Problem.

Daher in so einem Fall immer einen Copy Constructor machen.
Auch wenn man das Objekt als Referenz übergibt.

Man kann das leicht bei einer Methoden Deklaration vergessen und findet nacher den Fehler ziemlich schwer.



Ich vermutete weiter das am ende der Klasse der Destruktor der Kopie aufgerufen wird. Und hier ist die komische entdeckung:
als die Funktion beendet wird, so werden aus irgenteinem Grund ZWEI Destruktoren aufgerufen?????

Beim weiteren Testen ist mir weiterhin aufgefallen das das nur der Fall ist wenn ich mit dem GNU Compiler unter Linux programmiere. Unter Win mit VC++ wird nur ein Destruktor aufgerufen.


Hmm, interessant.
Muß ich ausprobieren.

Ciao,
_

anda_skoa
14-11-2002, 17:51
Hmm, nicht hier:
Folgendes mit GCC2.96 (ja,ja RH) kompiliert



#include <iostream>

using namespace std;

class Test
{
public:
Test(int i) : m_num(i) {
cout << "Test(" << i <<")" << endl;
}

Test(const Test& t) {
m_num = t.m_num;
cout << "Test(const Test&)" << endl;
}

~Test() {
cout << "~Test()" << endl;
}

void print() {
cout << m_num;
}

private:
int m_num;
};

void test(Test t)
{
cout << "funktion test: ";
t.print();
cout << endl;
}

int main()
{
Test t(1);
cout << "erzeugt" << endl;
test(t);

cout << "ende" << endl;
}


Der Destructor wird nur einmal nach Ende der Funktion test aufgerufen.

Ciao,
_

farell
15-11-2002, 16:13
Hi,

erst einmal danke für die schnelle antwort.
Also das mit dem Copy Construktor habe ich nicht so ganz verstanden (bin nicht so sehr vertraut mit C/C++), spielt aber auch keine Rolle.
Auf unserem Suse Server läuft glaub ich die Version 2.95.2 von dem g++ compiler.

Da ich aus diesem Programm nicht jede Zeile genau verstanden habe, ist hier der Quellcode mit dem ich dieses Problem festgestellt habe:



#include<iostream>

/*------------------------------------------------------------*/
/* die Schnittstelle der Klasse clsZahl wird implementiert */
class clsZahl {
private:
int z1;
public:
clsZahl(const int i, const int j); /* Konstruktor */
~clsZahl(); /* Destruktor */
void show(); /* Methode von clsZahl */
void set(int i); /* Methode von clsZahl */
};

/* die Klasse clsZahl wird implementiert */
clsZahl::clsZahl(const int i, const int j=0) {
this->z1=i;
cout << " clsZahl Konstruktor: ";
show();
}

clsZahl::~clsZahl() {
cout << " clsZahl Destruktor: ";
show();
}

void clsZahl::show() {
cout << "z1 = " << this->z1 << "\n";
}

void clsZahl::set(int i) {
this->z1=i;
}
/* die programierung der Klasse clsZahl ist beendet */



/*--------------------------------------------------------------*/
/* die Schnittstelle der Klasse clsUSE wird implementiert */
class clsUSE {
public:
clsUSE();
~clsUSE();
void rechne(clsZahl x);
};

/* die Klasse clsUSE wird implementiert */
clsUSE::clsUSE() {
cout << " clsUSE Konstruktor\n";
}

clsUSE::~clsUSE() {
cout << " clsUSE Destroktor\n";
}

void clsUSE::rechne(clsZahl x) {
x.show();
x.set(12);
x.show();
}
/* die programmierung der Klasse clsUSE ist beendet */



/*-------------------------------------------------------------------*/
/* struktorierter Teil von c im Main */

int main() {

clsZahl objZZ(5);
clsUSE objUSE;

cout << "- beginn der Funktion mit Objekt -\n";
objUSE.rechne(objZZ);
cout << "- ende -\n";


}
/* der struktorierte Teil von Main ist beendet*/


Wenn ich das Kompiliere und ausführe, erhalte ich folgende ausgabe:


clsZahl Konstruktor: z1 = 5
clsUSE Konstruktor
- beginn der Funktion mit Objekt -
z1 = 5
z1 = 12
clsZahl Destruktor: z1 = 12
clsZahl Destruktor: z1 = 5
- ende -
clsUSE Destroktor
clsZahl Destruktor: z1 = 5


Ich habe auch schon ausprobiert wenn ich das Objekt nicht an eine Methode einer Klasse übergebe sondern wie in deinem Beispiel an eine normale C-Funktion. Klappt jedoch auch nicht.
Ich habe das Programm auch schon auf einem g++-compieler einer Knoppix-Linux Distribution ausprobiert und den selben Sachverhalt festgestellt.

Vielleicht habe ich auch nur bei der Programmierung einen fehler gemacht. Wenn das so ist könnte mir dann bitte jemand sagen wo.

Gruß andreas

tkortkamp
15-11-2002, 16:30
Hi!

Nachdem ich dein Programm so modifiziert hatte, das es auch mit g++ 3.2 kompiliert (füg using namespace std; nach dem include ein) bekam ich folgenden Output:


clsZahl Konstruktor: z1 = 5
clsUSE Konstruktor
- beginn der Funktion mit Objekt -
z1 = 5
z1 = 12
clsZahl Destruktor: z1 = 12
- ende -
clsUSE Destroktor
clsZahl Destruktor: z1 = 5

Eigentlich kann ich mir nicht vorstellen das das der Visual C++ Compiler anders macht...

Zu deinem Problem:


void clsUSE::rechne(clsZahl x) {

Du übergibst der Funktion eine Kopie von deinem Objekt, d.h. es wird kopiert beim aufrufen der Funktion (=> 2 Desktruktoraufrufen).
Lösung: Benutze Referenzen:


void clsUSE::rechne(clsZahl &x) {

Dabei übergibst du der Funktion die Addresse von deinem Objekt (=> 1. Destruktoraufruf)

Danach gibt dein Programm folgendes aus:


clsZahl Konstruktor: z1 = 5
clsUSE Konstruktor
- beginn der Funktion mit Objekt -
z1 = 5
z1 = 12
- ende -
clsUSE Destroktor
clsZahl Destruktor: z1 = 12


Näheres zu Referenzen findest du bestimmt in deinem C++-Buch.

c ya,
Tobias

anda_skoa
15-11-2002, 16:39
Bekomme den selben Output.

Liegt offenbar am Fehlen des Copy Contructors.
Wenn ich nämlich einen solchen einfüge, kommt das heraus



clsZahl Konstruktor: z1 = 5
clsUSE Konstruktor
- beginn der Funktion mit Objekt -
z1 = 5
z1 = 12
clsZahl Destruktor: z1 = 12
- ende -
clsUSE Destroktor
clsZahl Destruktor: z1 = 5


der Copy Constructor sieht dabei so aus



class clsZahl {
clsZahl(const clsZahl& z) {
z1 = z.z1;
}
};


Copy Constructor
Ein Copy Constructor wird benutzt, wenn eine Kopie einer Instanz zereugt werden muß.
Zum Beispiel bei der Parameterübergabe, wenn call-by-value gemacht wird.

Signatur ist immer so:


Classname(const Classname & other);


other ist die Instanz, die kopiert werden soll.

Ciao,
_

tkortkamp
15-11-2002, 16:49
clsZahl Konstruktor: z1 = 5
clsUSE Konstruktor
- beginn der Funktion mit Objekt - [ clsZahl wird kopiert]
z1 = 5
z1 = 12
clsZahl Destruktor: z1 = 12 [Funktion zu Ende clsZahlKopie wird vernichtet]
- ende -
clsUSE Destroktor
clsZahl Destruktor: z1 = 5 [z1 sollte jetzt 12 sein...]

Das ist doch falsch? Da wird der DTOR schon wieder 2 mal aufgerufen.

c ya,
Tobias

anda_skoa
15-11-2002, 17:19
Original geschrieben von tkortkamp
Das ist doch falsch? Da wird der DTOR schon wieder 2 mal aufgerufen.


Nein, passt schon.
Mein Version war nur mit Copy Ctor, aber ohne Referenz.

Ciao,
_

tkortkamp
15-11-2002, 18:03
Alles klar!

Der g++ 3.2 fügt anscheinend eine Copy-Konstruktor automatisch dazu.

c ya,
Tobias

farell
16-11-2002, 17:12
Dann Danke ich euch erst einmal für eure Hilfe.
In Zukunft werde ich wohl die Objekte als Referanz übergeben, das macht glaub ich vieles einfacher.

Eine Frage zum Verständniss hätte ich dann noch.
Habe ich das jetzt richtig verstanden das wenn ich ein Objekt an eine Funktion, ohne den Copy Construktor zu verwenden, übergebe, das dann quasi zwei Objekte in der Funktion erstellt werden? Das übergebene Objekt (eine Kopie des im Main vorhandenen Orginals) bleibt unverändert, und ich arbeite mit einer weiteren Kopie des - schon kopierten - Objektes (Sonst lassen sich ja nicht die beiden Destruktoren erklären).
Steckt hinter diesem Mechanismuss ein tieferer Sinn den ich einfach nicht verstehe??

Gruß andreas

anda_skoa
16-11-2002, 17:17
Original geschrieben von farell
Dann Danke ich euch erst einmal für eure Hilfe.
In Zukunft werde ich wohl die Objekte als Referanz übergeben, das macht glaub ich vieles einfacher.


Ist auch viel effizienter.
Objekte erzeugen und kopieren ist "teuer".



Eine Frage zum Verständniss hätte ich dann noch.
Habe ich das jetzt richtig verstanden das wenn ich ein Objekt an eine Funktion, ohne den Copy Construktor zu verwenden, übergebe, das dann quasi zwei Objekte in der Funktion erstellt werden?


Sieht so aus.
Scheint aber ein Sonderfall im GCC2.9x zu sein
Denn wenn ich tkorkamp richtig verstanden habe, tritt das beim GCC3.2 nicht mehr auf.
Möglicherweise ein Compilier Bug.

Ciao,
_