Forum: PC-Programmierung C++ Destruktoraufruf nach Zuweisung verhindern


von Modularier (Gast)


Lesenswert?

Guten Morgen,

ich habe eine Klasse, die im Konstruktor Speicher dynamisch reserviert 
und diesen im Destruktor wieder freigibt. Von der Klasse werden ein paar 
Operatoren überladen, unter anderem der = Operator.
1
class cTest
2
{
3
public:
4
  float *elements;
5
  
6
  cTest()
7
  {
8
    elements = 0;
9
    std::cout << "Konstruktor ohne Parameter aufgerufen" << std::endl;
10
  }
11
12
  cTest(int m)
13
  {
14
    elements = new float[m];
15
    std::cout << "Speicher an Adresse " << elements << " reserviert" << std::endl;
16
  }
17
18
  ~cTest()
19
  {
20
    std::cout << "Speicher an Adresse " << elements << " freigegeben" << std::endl;
21
    delete [] elements;
22
  }
23
24
  cTest operator=(const cTest &test)
25
  {
26
    elements[0] = test.elements[0];
27
    std::cout << "Operator = (von " << test.elements << " nach " << elements << std::endl;
28
    return *this;
29
  }
30
31
  cTest operator+(const cTest &summand)
32
  {
33
    cTest *sum = new cTest(1);
34
    sum->elements[0] = elements[0] + summand.elements[0];
35
    std::cout << "Addition" << std::endl;
36
37
    return *sum;
38
  }
39
};


Nun möchte ich diese Klasse mit folgendem Code testen:
1
int main(void)
2
{
3
  cTest test(1);
4
  cTest test2(1);
5
  test = test2 + test;
6
  return 0;
7
}

Leider scheint etwas mit dem = Operator nicht zu stimmen. Wenn ich wie 
in diesem Beispiel
1
test = test + test2;
 ausführe, dann wird nach dem Aufruf des = Operators der Destruktor "der 
linken Seite", in diesem Fall "test" ausgeführt.

Spätestens beim return 0 stürzt das Programm ab, weil der Speicher für 
"test" ein zweites mal freigegeben wird.

Gleiches passiert ohne der Addition (z.B.
1
test = test2
).

Ich würde gerne wissen, warum der Destruktor nach der Zuweisung 
aufgerufen wird. Hat jemand einen Tipp, wie ich das verhindern kann?

Vielen Dank!

von Fritz G. (fritzg)


Lesenswert?

Ich kenne mich in C++ nicht sonderlich aus.
Aber ich denke folgendes:
test zeigt auf ein Objekt. In der Zuweisung verschwindet das Objekt ja, 
da es ja einem neuen Objekt zugewiesen wird. Deswegen wird erst der 
Destruktor aufgerufen.

von Modularier (Gast)


Lesenswert?

Ja, das ist es. Das ganze funktioniert jetzt indem der =Operator eine 
Referenz als Rückgabewert hat:
1
cTest &operator=(const cTest &test)
2
  {
3
    elements[0] = test.elements[0];
4
    std::cout << "Operator = (von " << test.elements << " nach " << elements << std::endl;
5
    return *this;
6
  }

Vielen Dank! :-)

von Wilhelm M. (wimalopaan)


Lesenswert?

In der Klasse ist / war einiges falsch:

- der Rückgabetyp des op= muss per-ref sein (gast Du ja schon gemerkt), 
damit keine Kopier erzeugt wird, dessen dtor Du dann gesehen hast.

- ggf. sollen ja auch Kettenzweisungen möglich sein

- Du solltest Elementinitialisierer / Initialisierungsliste in den 
ctor-en verwenden, statt Wertzuweisungen in den ctor-Rümpfen

- Datenelemente von primitiven DT (s.a. float* elements) sollten immer 
(in-class) initialisiert werden

- ich vermute, Du willst in op= alle Elemente des float-arrays kopieren. 
Schau Dir mal das copy-swap-idion an ...

- den binären op+ schreibt man oft als freie Funktion (Symmetrie) und 
macht auch einen op+= dazu (op+ ruft op+= auf -> kanonische 
Realisierung)

- die Heap-Allokation in op+ ...

Insgesamt schaut das nach einem Java-Programmierer aus ;-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Un Du solltest er gar nicht nach einer Möglichkeit suchen, den dtor 
nicht auszuführen. Denn er (der dtor mit seiner synchronen Ausführung) 
ist einer der besten Zutaten zu C++ (s.a. solche Idiome wie RAII).

von Sebastian V. (sebi_s)


Lesenswert?

Wilhelm M. schrieb:
> In der Klasse ist / war einiges falsch:

- Es fehlt ein Copy-Constructor

- Falls es keinen besonderen Grund gibt, das Rad nicht neu erfinden und 
vector statt eigener Speicherverwaltung nutzen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Sebastian V. schrieb:
> Wilhelm M. schrieb:
>> In der Klasse ist / war einiges falsch:
>
> - Es fehlt ein Copy-Constructor

Ja (das war in meinen ... enthalten ;-))

Vielleicht sollte der TO auch mal bei rule-of-three oder rule-of-five 
nachsehen ...

von Modularier (Gast)


Lesenswert?

Hallo,

Vielen Dank für die zahlreichen Tipps! Ich komme aus der C und C# Ecke. 
Die Verwendung eines Vektors ist vielleicht keine so gute Idee, da die 
elements eigentlich ein 2-dimansinales Array sind. Und soo schwierig 
finde ich die selbst gemacht Speicherverwaltung jetzt auch (noch) nicht. 
Außerdem soll das Programm auch eines Tages auf nem µC laufen, und dort 
brauche ich natürlich so abgespeckte und effiziente Klassen wie möglich.

von B. S. (bestucki)


Lesenswert?

Modularier schrieb:
> Die Verwendung eines Vektors ist vielleicht keine so gute Idee, da die
> elements eigentlich ein 2-dimansinales Array sind.

Du kannst die Umwandlung von zweidimensionalen Koordinaten zu einem 
eindimensionalen Index innerhalb der Klasse erledigen. Von aussen 
Verhält sich die Klasse wie ein zweidimensionales Array, intern benutzt 
sie einen Vektor.

Modularier schrieb:
> Und soo schwierig
> finde ich die selbst gemacht Speicherverwaltung jetzt auch (noch) nicht.

Wenn immer möglich vermeiden. Dies erleichtert dir die Arbeit enorm und 
verhindert viele Fehlerquellen. Z.B. dein operator= tut nicht das, was 
ich von ihm erwarten würde. Überlege dir, was geschehen würde, wenn 
folgender Code ausgeführt würde:
1
cTest A{};
2
cTest B{3};
3
A = B; // Zugriff auf ungültigen Zeiger

Modularier schrieb:
> Außerdem soll das Programm auch eines Tages auf nem µC laufen, und dort
> brauche ich natürlich so abgespeckte und effiziente Klassen wie möglich.

Benötigst du wirklich dynamischen Speicher oder stehen die Grössen oder 
zumindest Maximalgrössen deiner Arrays zur Compilezeit fest?


Einen universell einsetzbaren Container inklusive eigener 
Speicherverwaltung zu schreiben ist nicht ganz einfach. Versuche es zu 
vermeiden.


Nachtrag:
operator+ produziert ein Memory Leak. Lass das new weg und erzeuge die 
Variable sum statisch. Dazu benötigst du aber noch einen 
move-Konstruktor.


Nachtrag 2:
GCC wirft bei mir folgende Warnungen:
1
warning: ‘class cTest’ has pointer data members [-Weffc++]
2
warning:   but does not override ‘cTest(const cTest&)’ [-Weffc++]
3
In constructor ‘cTest::cTest()’:
4
warning: ‘cTest::elements’ should be initialized in the member initialization list [-Weffc++]
5
warning: zero as null pointer constant [-Wzero-as-null-pointer-constant]
6
In constructor ‘cTest::cTest(int)’:
7
warning: ‘cTest::elements’ should be initialized in the member initialization list [-Weffc++]
8
In member function ‘cTest cTest::operator=(const cTest&)’:
9
warning: ‘operator=’ should return a reference to ‘*this’ [-Weffc++]

: Bearbeitet durch User
von Tom (Gast)


Lesenswert?

Wenn vector verboten ist, kann man zumindest, statt den rohen Pointer zu 
speichern, den Compiler bitten, die Speicherfreigabe kostenfrei zu 
übernehmen:
[c]
...
  std::unique_ptr<float[]> elements;

  cTest()
  {
     //elements ist automatisch 0
  }

  ~cTest()
  {
    // elements[] wird automatisch freigegeben.
  }

  cTest(int m) : elements(new float[10])
  {
  }
  ...
  std::unique_ptr<float[]> elements;
[c]

Zweifelhafte Konstruktionen compilieren so auch nicht, bei 
Zuweisungsoperatoren etc. muss man sich Gedanken machen, was mit dem 
Speicher passieren soll. Das muss man bei manuellem Speichergefrickel 
eigentlich auch, aber da kann man das Problem erstmal ignorieren und 
dafür später seltsame Abstürze debuggen.

von Tom (Gast)


Lesenswert?

Die 10 soll ein m sein.

von Vlad T. (vlad_tepesch)


Lesenswert?

Du solltest den "Test(int)" Konstruktor als "explicit" markieren. Dies 
sollte man immer tun, wenn man Klassen definiert, deren Konstruktoren 
nur einem atomaren Basistyp-Parameter haben und die ein anderes Konzept 
als den Basistyp implementieren, also das erzeugte Objekt nicht nur eine 
andere, gleichwertige Repräsentation des Arguments ist.

Beispiel: bei ner BigInt klasse ist implizite Konstruktion aus nem 
Integer ja okay, aber ne Matrix implizit aus nem Integer zu erzeugen is 
schon komisch

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.