Sie sind nicht angemeldet.

Lieber Besucher, herzlich willkommen bei: VDR Portal. Falls dies Ihr erster Besuch auf dieser Seite ist, lesen Sie sich bitte die Hilfe durch. Dort wird Ihnen die Bedienung dieser Seite näher erläutert. Darüber hinaus sollten Sie sich registrieren, um alle Funktionen dieser Seite nutzen zu können. Benutzen Sie das Registrierungsformular, um sich zu registrieren oder informieren Sie sich ausführlich über den Registrierungsvorgang. Falls Sie sich bereits zu einem früheren Zeitpunkt registriert haben, können Sie sich hier anmelden.

1

Sonntag, 27. Januar 2008, 21:25

std::map Verständnis Frage

Hallo,

mir fehlt das Verständnis für folgenden Code:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cat test.cpp

#include <stdlib.h>
#include <map>

class test
{
public:
   ~test()
   {
      printf("~test\n");
   }
};

int main()
{
   std::map<int,test> m_map;

//   test t;

   m_map[1] = *(new test());

   printf("exit\n");

   return 0;
}


Die Ausgabe lautet:

~test
~test
exit
~test


Mir ist vollkommen unklar wieso beim Einfügen der Instanz von 'test' der Konstruktor zweimal aufgerufen wird. Die Map ist doch eigentlich nur ein Container, der den Inhalt eigentlich nicht anfassen sollte.

Man möge mich erhellen :schiel

Gruß
Sebastian

2

Sonntag, 27. Januar 2008, 21:44

Diese Zeile weist keine Referenz zu, sondern kopiert das Objekt, deshalb kommen mehrere Instanzen zustande.
m_map[1] = *(new test());

Wenn Du statt dem Objekt, nur einen Pointer auf das Objekt verwendet,
dann kann dieser Kopiert werden ohne das es stört, man muss sich aber selber
um die Lebenszeit und das Löschen per delete kümmern.

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdlib.h>
#include <map>

class test
{
public:
   ~test()
   {
      printf("~test\n");
   }
};

int main()
{
   std::map<int,test*> m_map;

//   test t;

   m_map[1] = (new test());

   printf("exit\n");

   delete m_map[1];
   return 0;
}


Andreas
System: VDR 2.2 unter Debian Wheezy
Aktive Projekte : Radiorecorder Web GUI | targa VFD Plugin | XXV - Xtreme eXtension for VDR

zirias

Fortgeschrittener

Beiträge: 343

Wohnort: Umgebung KA

Beruf: Dipl.-Inform.

  • Nachricht senden

3

Montag, 28. Januar 2008, 11:55

RE: std::map Verständnis Frage

Zitat

Original von zuse

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
   std::map<int,test> m_map;

//   test t;

   m_map[1] = *(new test());

   printf("exit\n");

   return 0;
}


Die Ausgabe lautet:

~test
~test
exit
~test


Dass es 3 Kopien sind, ist leicht erklärt. Die erste erstellst du mit dem new-Operator. Die zweite ist ein sogenanntes temporary object: da die rechte Seite deiner Zuweisung ein Ausdruck ist (dereferenzierung von irgendwas) wird hier ein temporäres Objekt mit dem Ergebnis des Ausdrucks erstellt, dessen Lebensdauer sich auf die eine Anweisung beschränkt. Für die dritte Kopie ist dann std::map<> verantwortlich, denn das ist, wie du schon sagst, ein Container. Wenn es Objekte einer Klasse speichern soll tut es auch genau das (und speichert nicht etwa Pointer oder Referenzen), also muss es das Objekt kopieren, das gespeichert werden soll.

Was mich bei deiner Ausgabe wundert ist, dass der Destruktor der ersten Kopie überhaupt aufgerufen wird. Da du den Rückgabewert von new nicht speicherst sondern direkt weiterverarbeitest (in dem Fall mit *), ist der Pointer zu deiner ersten Kopie eigentlich verloren, das sollte normalerweise ein Speicherleck sein.

Was du wahrscheinlich tun willst ist wie von meinem Vorposter schon vorgeschlagen, Pointer im Container zu speichern. Diese bergen natürlich Gefahren, sobald der Code etwas komplexer wird und nicht mehr auf einen Blick klar ist, wo und wann das Objekt zerstört werden muss -- eventuell willst du dir "Smart Pointers" anschauen.
Asrock A75 Pro4-M
Debian wheezy (testing, stock) (aktuell 2012-08-24: Linux 3.2, VDR 1.7.28)
vdr-sxfe (xineliboutput)
Pioneer VSX-520-K

4

Freitag, 1. Februar 2008, 02:30

RE: std::map Verständnis Frage

Zitat

Originally posted by zuse
[...]
Mir ist vollkommen unklar wieso beim Einfügen der Instanz von 'test' der Konstruktor zweimal aufgerufen wird. Die Map ist doch eigentlich nur ein Container, der den Inhalt eigentlich nicht anfassen sollte.
[...]


<stdlib.h> ersetzt du bitte durch <iostream>
und
printf("whatever"); ersetzt du bitte durch std::cout << "whatever" << std::endl;

ausgehend von std::map<int, test>:

m_map[1] = *(new test()); <--- falsch + gefaehrlich

m_map[1] = test(); <--- richtig

Das von dir bebachtete Verhalten sind fundamentale Eigenschaften von C++.
Da du keinen copy ctor definiert hast, erstellt der C++ Compiler für dich einen default copy ctor. Von deinem erstellten Objekt werden bei verschiedenen Assignment-Operationen kurzfristige, für dich nicht ersichtliche Kopien erstellt. Dies sind aber ganz normale Instanzen deiner Klasse, dessen dtor nach der Verwendung ausgeführt wird. Genau dieses Verhalten hast du beobachtet!

Wichtig ist hier auch:
m_map[1] erstellt zuerst! ein neues Objekt vom Typ 'test', fügt es in die map ein und gibt dann eine Referenz auf das neue Objekt zurück. Im nächsten Schritt wird der Assignment-Operator operator=(const test& rhs) dieses Objekts aufgerufen mit test() als Argument. Das Argument wird ebenfalls nach der Assignment-Operation zerstört.


Btw. wenn du mit new arbeitest, schau dir vorher std::auto_ptr<> bzw. boost::shared_ptr<> und boost::weak_ptr<> an!


Zitat

Originally posted by zirias
Dass es 3 Kopien sind, ist leicht erklärt. Die erste erstellst du mit dem new-Operator. Die zweite ist ein sogenanntes temporary object: da die rechte Seite deiner Zuweisung ein Ausdruck ist (dereferenzierung von irgendwas) wird hier ein temporäres Objekt mit dem Ergebnis des Ausdrucks erstellt, dessen Lebensdauer sich auf die eine Anweisung beschränkt. Für die dritte Kopie ist dann std::map<> verantwortlich, denn das ist, wie du schon sagst, ein Container. Wenn es Objekte einer Klasse speichern soll tut es auch genau das (und speichert nicht etwa Pointer oder Referenzen), also muss es das Objekt kopieren, das gespeichert werden soll.


Das stimmt so leider nicht.


Zitat

Originally posted by zirias
Was mich bei deiner Ausgabe wundert ist, dass der Destruktor der ersten Kopie überhaupt aufgerufen wird. Da du den Rückgabewert von new nicht speicherst sondern direkt weiterverarbeitest (in dem Fall mit *), ist der Pointer zu deiner ersten Kopie eigentlich verloren, das sollte normalerweise ein Speicherleck sein.


Der Destruktor des durch new() instantiierten Objekts wird in diesem Beispiel nie ausgefuehrt.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »crib« (1. Februar 2008, 04:02)


zirias

Fortgeschrittener

Beiträge: 343

Wohnort: Umgebung KA

Beruf: Dipl.-Inform.

  • Nachricht senden

5

Montag, 4. Februar 2008, 17:41

RE: std::map Verständnis Frage

Zitat

Original von crib
m_map[1] erstellt zuerst! ein neues Objekt vom Typ 'test', fügt es in die map ein und gibt dann eine Referenz auf das neue Objekt zurück. Im nächsten Schritt wird der Assignment-Operator operator=(const test& rhs) dieses Objekts aufgerufen mit test() als Argument. Das Argument wird ebenfalls nach der Assignment-Operation zerstört.

So ganz stimmt das immer noch nicht, die rechte Seite ist nicht test() sondern *(new test()) und damit ein temporary...

Zitat

Das stimmt so leider nicht.

Naja, hier war ich wohl in Gedanken bei std::vector<>::push_back(). Aber bis auf die Kopie beim Einfügen stimmt's schon...

Zitat

Zitat

Originally posted by zirias
Was mich bei deiner Ausgabe wundert ist, dass der Destruktor der ersten Kopie überhaupt aufgerufen wird. Da du den Rückgabewert von new nicht speicherst sondern direkt weiterverarbeitest (in dem Fall mit *), ist der Pointer zu deiner ersten Kopie eigentlich verloren, das sollte normalerweise ein Speicherleck sein.


Der Destruktor des durch new() instantiierten Objekts wird in diesem Beispiel nie ausgefuehrt.


Das sehe ich ja genauso. Aber woher kommt der dritte Destruktor-Aufruf? Egal wie ich es drehe, ich sehe bei dem Code abgesehen vom mit new konstruierten Objekt nur zwei weitere Objekte.

Und etwas OT: Ob IOStreams oder Stdlib ist durchaus Geschmackssache. IOStreams ist eben die neuere und objektorientierte Lösung, aber deshalb nicht unbedingt in JEDEM Fall die beste Wahl. Wenn man sich die Mechanismen dahinter mal anschaut, KANN das auch mal mit Kanonen auf Spatzen sein ;)
Asrock A75 Pro4-M
Debian wheezy (testing, stock) (aktuell 2012-08-24: Linux 3.2, VDR 1.7.28)
vdr-sxfe (xineliboutput)
Pioneer VSX-520-K

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »zirias« (4. Februar 2008, 17:41)


6

Montag, 4. Februar 2008, 20:44

Zitat

So ganz stimmt das immer noch nicht, die rechte Seite ist nicht test() sondern *(new test()) und damit ein temporary...

Der Ausdruck "*(new test())" ist in sich bereits ein memleak und damit ein schwerer Fehler. Was du mit new allokierst, musst du auch wieder frei geben.
Aber selbst wenn du es wieder frei geben würdest, es wäre trotzdem schlechter Code. Das temporäre Objekt gehört auf den Stack, wie in meinem Beispiel!

Zitat

Naja, hier war ich wohl in Gedanken bei std::vector<>::push_back(). Aber bis auf die Kopie beim Einfügen stimmt's schon...

Nein tut es nicht.

Zitat

Das sehe ich ja genauso. Aber woher kommt der dritte Destruktor-Aufruf? Egal wie ich es drehe, ich sehe bei dem Code abgesehen vom mit new konstruierten Objekt nur zwei weitere Objekte.


Insgesamt werden VIER Objekte erstellt und damit VIER Konstruktoren und VIER Destruktoren ausgeführt!

Im Detail:

Wir gehen aus von:
std::map<int, test> mymap;
mymap[1] = test();

mymap[1] führt zu:

neues objekt wird erstellt (1)
kopie von (1) wird erstellt (2)
kopie von (2) wird erstellt (3)
(1) wird zerstoert
(2) wird zerstoert
(3) befindet sich jetzt in der map

bis hierher ist es das interne Verhalten von std::map<>

jetzt kommt unsere Zuweisung: ' = test()'

temporaeres objekt wird erstellt (4)
der assignment-operator von (3) wird mit (4) als parameter aufgerufen
(4) wird zerstoert

Am Ende des Programms wird mymap zerstört und damit alle Destruktoren der enthaltenen Objekte aufgerufen, dies führt zu:

(3) wird zerstört

----------------------------------------

Dieses elementare Verhalten zu verstehen ist ungemein wichtig, wenn man C++ programmieren will. Insbesondere den Code, den der C++ Compiler selber erzeugt, ohne dass man davon weiss (copy ctor, assigment operator, ...).

7

Montag, 4. Februar 2008, 20:47

Noch was:

Schreibe die Klasse 'test' mit einem default ctor, einem copy ctor und einem assignment operator und schau, was folgender Code macht:

mymap[1];

Dann geht dir vielleicht ein Licht auf. ;)

LordJaxom

Meister

Beiträge: 2 115

Wohnort: Kind der Kirmes am Kanal

Beruf: System- und Anwendungsprogrammierer

  • Nachricht senden

8

Dienstag, 5. Februar 2008, 08:38

Der Grund warum in der Map so viel kopiert wird, ist, dass die Map intern ein Container von pair<key_type, mapped_type> ist. Dröseln wir den Code mal auf:

Quellcode

1
2
3
4
m_map[1] = test(); // entspricht:

test& t = m_map[1]; // m_map.operator[](1)
t = test(); // t.operator=(test());


Beim ersten Zugriff auf m_map[1] wird ein insert(end(), pair<int, test>(1, test())) aufgerufen. Hierbei wird ein temporäres test als Argument für den pair-Konstruktor erzeugt ("test()") und ein test als Member second innerhalb des pair selbst (welches aus dem Argument Copy-Konstruiert wird). Das Objekt vom Typ pair ist selbst natürlich auch temporär. Bis hierher haben wir schon zwei Objekte.

Innerhalb dieser Methode insert, welche eine Referenz auf ein pair als Parameter erhalten hat, wird nun ein Knoten im Baum erzeugt, an dessen Stelle das pair wiederum Copy-Konstruiert wird. Dieses dritte Objekt lebt nun erst tatsächlich im Baum.

Nach dem Abarbeiten des Statements m_map[1] werden die beiden temporären Objekte wieder abgebaut, wodurch zwei Destruktoren durchlaufen werden. Das Ergebnis von m_map[1] ist eine Referenz auf test.

Diesem Objekt, dessen Referenz wir in t speichern, weisen wir ein weiteres temporäres Objekt zu (4. Konstruktor). Dabei wird der Assignment-Operator aufgerufen und der Inhalt des temporären Objekts in das Objekt in der Map kopiert. Danach wird das temporäre Objekt wieder zerstört (3. Destruktor).

Am Ende des Programms wird natürlich die Map wieder abgebaut, wobei jedes darin enthaltene Objekt zerstört wird (4. Destruktor).

zirias

Fortgeschrittener

Beiträge: 343

Wohnort: Umgebung KA

Beruf: Dipl.-Inform.

  • Nachricht senden

9

Dienstag, 5. Februar 2008, 10:55

Zitat

Original von crib

Zitat

So ganz stimmt das immer noch nicht, die rechte Seite ist nicht test() sondern *(new test()) und damit ein temporary...

Der Ausdruck "*(new test())" ist in sich bereits ein memleak und damit ein schwerer Fehler. Was du mit new allokierst, musst du auch wieder frei geben.
Aber selbst wenn du es wieder frei geben würdest, es wäre trotzdem schlechter Code. Das temporäre Objekt gehört auf den Stack, wie in meinem Beispiel!

Und wo hab ich geschrieben, dass das richtig sei? Es geht um nichts anderes als alle Instanzen des Objekts zu finden. Ist ja toll, dass du wiederholt darauf hinweist, wie fürchterlich falsch das ist, aber das hat der OP schon zweimal gesagt bekommen, bevor du überhaupt in den Thread eingestiegen bist.

Zitat

Zitat

Naja, hier war ich wohl in Gedanken bei std::vector<>::push_back(). Aber bis auf die Kopie beim Einfügen stimmt's schon...

Nein tut es nicht.

Doch, tut es. Eine Instanz durch new, eine durch temporary und eine in der Map haben wir, genau wie ich geschrieben habe. Die vierte hat mir gefehlt, das hat jetzt aber LordJaxom erfreulicherweise aufgeklärt (durch std::pair). Es reicht als Erklärung eben nicht ganz aus zu sagen, dass gleich zweimal kopiert wird, man muss dann schon auch erfahren, warum ;)
Asrock A75 Pro4-M
Debian wheezy (testing, stock) (aktuell 2012-08-24: Linux 3.2, VDR 1.7.28)
vdr-sxfe (xineliboutput)
Pioneer VSX-520-K

Immortal Romance Spielautomat