You are not logged in.

Dear visitor, welcome to VDR Portal. If this is your first visit here, please read the Help. It explains in detail how this page works. To use all features of this page, you should consider registering. Please use the registration form, to register here or read more information about the registration process. If you are already registered, please login here.

1

Sunday, January 27th 2008, 9:25pm

std::map Verständnis Frage

Hallo,

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

Source code

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

Sunday, January 27th 2008, 9:44pm

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.

Source code

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.0 unter Debian Wheezy
Aktive Projekte : Radiorecorder Web GUI | DVD-Switch Plugin | targa VFD Plugin | XXV - Xtreme eXtension for VDR

zirias

Intermediate

Posts: 343

Location: Umgebung KA

Occupation: Dipl.-Inform.

  • Send private message

3

Monday, January 28th 2008, 11:55am

RE: std::map Verständnis Frage

Quoted

Original von zuse

Source code

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

Friday, February 1st 2008, 2:30am

RE: std::map Verständnis Frage

Quoted

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!


Quoted

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.


Quoted

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.

This post has been edited 1 times, last edit by "crib" (Feb 1st 2008, 4:02am)


zirias

Intermediate

Posts: 343

Location: Umgebung KA

Occupation: Dipl.-Inform.

  • Send private message

5

Monday, February 4th 2008, 5:41pm

RE: std::map Verständnis Frage

Quoted

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...

Quoted

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...

Quoted

Quoted

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

This post has been edited 1 times, last edit by "zirias" (Feb 4th 2008, 5:41pm)


6

Monday, February 4th 2008, 8:44pm

Quoted

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!

Quoted

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.

Quoted

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

Monday, February 4th 2008, 8:47pm

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. ;)

Posts: 2,101

Location: Kind der Kirmes am Kanal

Occupation: System- und Anwendungsprogrammierer

  • Send private message

8

Tuesday, February 5th 2008, 8:38am

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:

Source code

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

Intermediate

Posts: 343

Location: Umgebung KA

Occupation: Dipl.-Inform.

  • Send private message

9

Tuesday, February 5th 2008, 10:55am

Quoted

Original von crib

Quoted

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.

Quoted

Quoted

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