amiga-news ENGLISH VERSION
.
Links| Forum| Kommentare| News melden
.
Chat| Umfragen| Newsticker| Archiv
.

amiga-news.de Forum > Programmierung > C++ Problem: Objektfreigabe [ - Suche - Neue Beiträge - Registrieren - Login - ]

-1- 2 [ - Beitrag schreiben - ]

24.10.2004, 19:02 Uhr

Reth
Posts: 1812
Nutzer
Hallo allerseits,

habe als verwöhnter Javaprogrammierer ein C++ Problem.

Ich habe aus Javagewohnheit in meinem Programm viele dynamische und z.T. anonyme Objekte erstellt.

Für deren Verwaltung habe ich eine Listenklasse die mit einem Template arbeitet und deshalb Objekte jeglichen Typs verwalten kann.

Das Ganze sieht ungefähr so aus:

Object a = new A(new B());
list->addElement(a);

Damit ich mit dynamischen anonymen Objekten umgehen kann, gibt die Liste in ihrem Destruktor sämtliche Elemente frei. Dadurch bekomme ich keine Probleme mit den anonymen Objekten.

Nun benötige ich aber manche Objekte in unterschiedlichen Listen also in der Art:

list1->addElement(a); // um bei obigen Beispiel zu bleiben

Das führt natürlich zu dem Problem der doppelten Freigabe was verheerende Auswirkungen hat.

Ich vermute mal, dass mein Ansatz für C++ komplett falsch ist?!
Aber was ist der Richtige? Wie gehe ich in C++ mit dynamisch erzeugten anonymen Objekten um?

Bin für nen guten Tip sehr dankbar!

Ciao

[ - Antworten - Zitieren - Direktlink - ]

24.10.2004, 23:41 Uhr

whooha
Posts: 41
Nutzer
Vielleicht geht es, wenn Du jedem Objekt eine Counter-Variable spendierst.

Beim Hinzufügen zur Liste wird der Counter um eins erhöht
addElement(a){
..
a.counter++;
..
}

Und im Destruktor der Liste dann sowas wie
if(--element.counter == 0) löschen/freigeben
für jedes Element der Liste.

Also eine Art Semaphore, die anzeigt, ob das Objekt noch benutzt wird.

Nur sone Idee, hab mit C auch nicht so viel am Hut.


[ Dieser Beitrag wurde von whooha am 25.10.2004 editiert. ]

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 07:40 Uhr

Reth
Posts: 1812
Nutzer
Danke, gute Idee.
Denke auch, dass dies die einfachste Umsetzung ist, ohne alles neu konzeptionieren zu müssen.

Mich würde mal der generelle Ansatz in C++ für anonyme dynamische Objekete interessieren (falls es sowas gibt).

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 09:42 Uhr

Solar
Posts: 3674
Nutzer
Zitat:
Ich vermute mal, dass mein Ansatz für C++ komplett falsch ist?!

Ja. 8)

Zitat:
Wie gehe ich in C++ mit dynamisch erzeugten anonymen Objekten um?

Wieso willst Du sie überhaupt dynamisch erzeugen?

Zunächst einmal, ganz wichtig, begreife C++ als eigene Sprache, nicht als eine andere Möglichkeit Java zu programmieren.

Nicht jede Klasse muß von einem allgegenwärtigen "class Object" abgeleitet sein. Wenn zwei Klassen nicht miteinander verwandt sind, modelliere sie auch nicht als verwandt.

Nicht jedes Objekt muß mit "new" erstellt werden. (Im Gegenteil, oft wirst Du in einem gut entworfenen Programm kein einziges "new" brauchen.)

Mache Dich mit der Standardbibliothek vertraut. Dort wirst Du Dinge finden wie Container (vor allem <vector>), oder den auto_ptr (in <memory>).

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 11:49 Uhr

Reth
Posts: 1812
Nutzer
Hi auch,

Zitat:
Original von Solar:

Wieso willst Du sie überhaupt dynamisch erzeugen?


Nun das erscheint mir als die sinnvollste Möglichkeit zur Laufzeit Objekte von Dingen anzulegen, die vor dem Programmstart noch nicht bekannt sind. Wenn während des Programms neue Objekte notwendig sind, die vorher noch nicht absehbar waren, wie soll man das denn realisieren?

Zitat:
Zunächst einmal, ganz wichtig, begreife C++ als eigene Sprache, nicht als eine andere Möglichkeit Java zu programmieren.

Das wird schwierig genug :glow:

Zitat:
Nicht jede Klasse muß von einem allgegenwärtigen "class Object" abgeleitet sein. Wenn zwei Klassen nicht miteinander verwandt sind, modelliere sie auch nicht als verwandt.

Hab ich auch nicht.

Zitat:
Nicht jedes Objekt muß mit "new" erstellt werden. (Im Gegenteil, oft wirst Du in einem gut entworfenen Programm kein einziges "new" brauchen.)

Wie erstellt man dann die Objekte in diesem Falle?

Zitat:
Mache Dich mit der Standardbibliothek vertraut. Dort wirst Du Dinge finden wie Container (vor allem <vector>), oder den auto_ptr (in <memory>).

Mein momentan verwendetes Entwicklungssystem hat die SDL leider nicht!

Ciao

[ Dieser Beitrag wurde von Reth am 25.10.2004 editiert. ]

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 12:18 Uhr

Solar
Posts: 3674
Nutzer
Zitat:
Original von Reth:

Wenn während des Programms neue Objekte notwendig sind, die vorher noch nicht absehbar waren, wie soll man das denn realisieren?


Als lokale Variable?

Class var; // Standardkonstruktor
Class var("Hallo!"); // Class( char * ) oder Class( string& )
Class var = "Hallo!"; // Erzeugen eines anonymen Objekts und Zuweisung über operator=()

"new" legt ein Objekt auf dem Heap an, das solange existiert, bis es mit "delete" zerstört wird. Das "var" aus den obigen Beispielen wird automatisch zerstört, wenn es "out of scope" geht.

Oder anonym:

#include <vector>

using std::vector;

vector<Object> list;

list.push_back( Object("Hallo") );
list.push_back( Object("Welt") );

Zitat:
Zitat:
Zunächst einmal, ganz wichtig, begreife C++ als eigene Sprache, nicht als eine andere Möglichkeit Java zu programmieren.

Das wird schwierig genug :glow:


Bitte, bitte gib Dir Mühe. Ich habe mir ein Projekt an's Bein gebunden, bei dem ein Java-Progger genau das gemacht hat: In C++ Java zu programmieren. Grau-en-haft. Ich krieg' jedesmal Migräne, wenn ich versuche, den Code wieder ein Stück lesbarer zu machen. 8)

Zitat:
Zitat:
Mache Dich mit der Standardbibliothek vertraut. Dort wirst Du Dinge finden wie Container (vor allem <vector>), oder den auto_ptr (in <memory>).

Mein momentan verwendetes Entwicklungssystem hat die SDL leider nicht!


Du meinst STL, und Du hast ein Problem. :P

Du machst nicht zufällig mit MaxonC++ herum, oder StormC 3? ;)

Nein, ehrlich. C++ ohne STL ist ungefähr wie Java ohne 'import'. Dringend GCC 3.x besorgen! ;)

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 12:33 Uhr

Solar
Posts: 3674
Nutzer
Oh, zur Syntax:

new Object() gibt Dir einen Pointer auf ein Object, Object() gibt Dir ein Object.

STL-Container speichern Kopien der Objekte. Das ist OK, wenn Deine Objekte "billig" zu kopieren sind (d.h., nicht übermäßig groß / komplex).

Wenn sie "teuer" sind, oder Du unbedingt "new" verwenden möchtest, gibt es (ebenfalls in der STL) das Wrapper-Template auto_ptr<>. Das wird benutzt wie folgt:

auto_ptr<Object> aptr( new Object() );

Du kannst aptr wie einen Pointer auf Object verwenden. Der Pointer auf Object ist eine Membervariable von aptr, der syntaktisch transparent behandelt wird - *aptr ist vom Typ Object.

Der "Trick" bei auto_ptr<> besteht darin, daß bei einer Kopie (auto_ptr<Object> bptr = aptr) keine echte Kopie angelegt, sondern der Pointer auf Object "weitergereicht" wird - aptr ist nach der Zuweisung nicht mehr gültig. Und wenn bptr "out-of-scope" geht, wird (durch den Destruktor von auto_ptr<>) automatisch "delete" auf den enthaltenen Pointer aufgerufen.

Auf den ersten Blick nur verwirrend; aber extrem praktisch, wenn Du Pointer in einem Container speichern willst (wie oben beschrieben):

list.push_back( aptr );

In der Liste wird ein (anonymer) auto_ptr<Object> als Kopie von aptr angelegt. aptr ist ab sofort ungültig. Geht jetzt list out-of-scope, ruft es den Destruktor für alle enthaltenen Objekte auf - und das Objekt, auf das aptr ursprünglich gezeigt hat, wird "delete"'d.

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 12:37 Uhr

Reth
Posts: 1812
Nutzer
@Solar

Danke für die Antworten erstmal!

Zitat:
Original von Solar:
Zitat:
Original von Reth:

Wenn während des Programms neue Objekte notwendig sind, die vorher noch nicht absehbar waren, wie soll man das denn realisieren?


Als lokale Variable?

Class var; // Standardkonstruktor
Class var("Hallo!"); // Class( char * ) oder Class( string& )
Class var = "Hallo!"; // Erzeugen eines anonymen Objekts und Zuweisung über operator=()

"new" legt ein Objekt auf dem Heap an, das solange existiert, bis es mit "delete" zerstört wird. Das "var" aus den obigen Beispielen wird automatisch zerstört, wenn es "out of scope" geht.

Oder anonym:

#include <vector>

using std::vector;

vector<Object> list;

list.push_back( Object("Hallo") );
list.push_back( Object("Welt") );


Heisst das in meinem Fall, wenn ich eine Klasse A habe und ich diese mit

list.addElement(A("Param1", 2, 'C', TRUE));

in die Liste lege sie dort solange bleibt, bis die Liste zerstört wird, auch wenn die Methode und die Klasse, in der dieser Aufruf steht zwischenzeitlich verlassen wird?

Zitat:
Bitte, bitte gib Dir Mühe. Ich habe mir ein Projekt an's Bein gebunden, bei dem ein Java-Progger genau das gemacht hat: In C++ Java zu programmieren. Grau-en-haft. Ich krieg' jedesmal Migräne, wenn ich versuche, den Code wieder ein Stück lesbarer zu machen. 8)

Kann ich gern tun, irgendwelche Tips, jetzt wo Du den direkten Vergleich hast? Ich kenne zwar noch keinen umfangreicheren C++ Code, aber Java-Code ist im allgemeinen sehr gut lesbar, v.a. wegen der sprechenden Methodennamen etc.

Zitat:
Du meinst STL, und Du hast ein Problem. :P

Ich hoffe doch nur, weil ich sie nicht habe - oder siehtst Du bei mir noch sonstige Probleme? :shock2:

Zitat:
Du machst nicht zufällig mit MaxonC++ herum, oder StormC 3? ;)

Doch, in der Tat!

Zitat:
Nein, ehrlich. C++ ohne STL ist ungefähr wie Java ohne 'import'. Dringend GCC 3.x besorgen! ;)

Ist in der Planung!

Ciao

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 12:47 Uhr

Reth
Posts: 1812
Nutzer
Zitat:
Original von Solar:
Oh, zur Syntax:

new Object() gibt Dir einen Pointer auf ein Object, Object() gibt Dir ein Object.


Verstanden.

Zitat:
STL-Container speichern Kopien der Objekte. Das ist OK, wenn Deine Objekte "billig" zu kopieren sind (d.h., nicht übermäßig groß / komplex).

Wenn sie "teuer" sind, oder Du unbedingt "new" verwenden möchtest, gibt es (ebenfalls in der STL) das Wrapper-Template auto_ptr<>. Das wird benutzt wie folgt:

auto_ptr<Object> aptr( new Object() );

Du kannst aptr wie einen Pointer auf Object verwenden. Der Pointer auf Object ist eine Membervariable von aptr, der syntaktisch transparent behandelt wird - *aptr ist vom Typ Object.


Wie sieht das dann konkret aus?

Zitat:
Der "Trick" bei auto_ptr<> besteht darin, daß bei einer Kopie (auto_ptr<Object> bptr = aptr) keine echte Kopie angelegt, sondern der Pointer auf Object "weitergereicht" wird - aptr ist nach der Zuweisung nicht mehr gültig. Und wenn bptr "out-of-scope" geht, wird (durch den Destruktor von auto_ptr<>) automatisch "delete" auf den enthaltenen Pointer aufgerufen.

Auf den ersten Blick nur verwirrend; aber extrem praktisch, wenn Du Pointer in einem Container speichern willst (wie oben beschrieben):

list.push_back( aptr );

In der Liste wird ein (anonymer) auto_ptr<Object> als Kopie von aptr angelegt. aptr ist ab sofort ungültig. Geht jetzt list out-of-scope, ruft es den Destruktor für alle enthaltenen Objekte auf - und das Objekt, auf das aptr ursprünglich gezeigt hat, wird "delete"'d.


Und was passiert, wenn ein und dasselbe Objekt in 2 versch. Listen stehen soll? Dann zeigen doch die jeweiligen anonymen Autopointer auf das gleiche Objekt, es kann aber nur einmal freigegeben werden!? Sorgt Autopointer (bzw. dessen Implementierung) dann automatisch dafür?
Wenn eine Liste zerstört wird, die andere aber nicht (in beiden ist das gleiche Objekt eingetragen mittels aptr, wie auch immer das auszusehen hat), enthält die nicht-zerstörte Liste dann noch einen gültigen Zeiger auf das Objekt?

Oh weh, mit jeder Antowrt tun sich mehr fragen auf!

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 13:04 Uhr

Solar
Posts: 3674
Nutzer
Zitat:
Heisst das in meinem Fall, wenn ich eine Klasse A habe und ich diese mit

list.addElement(A("Param1", 2, 'C', TRUE));

in die Liste lege sie dort solange bleibt, bis die Liste zerstört wird, auch wenn die Methode und die Klasse, in der dieser Aufruf steht zwischenzeitlich verlassen wird?


Solange die Variable list besteht, ja.

Zitat:
Kann ich gern tun, irgendwelche Tips, jetzt wo Du den direkten Vergleich hast? Ich kenne zwar noch keinen umfangreicheren C++ Code, aber Java-Code ist im allgemeinen sehr gut lesbar, v.a. wegen der sprechenden Methodennamen etc.

Benamung von Variablen, Methoden und Klassen kannst Du ja beibehalten.

* "new" nur da, wo tatsächlich notwendig. "new" erfordert Pointer und Destruktoren; beides will man tunlichst vermeiden.

* Das Kernfeature von C++ sind nicht Klassen, sondern Templates. Wenn zwei Klassen keine gemeinsame Datenstrukturen haben, und Du die Polimorphie nur für gemeinsame Funktionen brauchst, prüfe, ob nicht ein Template eine bessere Lösung wäre als eine gemeinsame Basisklasse.

Zitat:
Zitat:
Du meinst STL, und Du hast ein Problem.
Ich hoffe doch nur, weil ich sie nicht habe - oder siehtst Du bei mir noch sonstige Probleme?

Nein, nur weil Du sie nicht hast. Es ist nun einmal der wichtigste Teil der C++-Standardbibliothek.

Zitat:
Zitat:
Du kannst aptr wie einen Pointer auf Object verwenden. Der Pointer auf Object ist eine Membervariable von aptr, der syntaktisch transparent behandelt wird - *aptr ist vom Typ Object.
Wie sieht das dann konkret aus?

#include <memory>

using std::auto_ptr;

void doSomething( MyClass& bar );

int foo()
{
auto_ptr< MyClass > myAPtr( new MyClass( "Hallo", true, 42 );

myAPtr->someMyClassMethod();
doSomething( *myAPtr );
}

Am Ende der Funktion foo() geht der (lokal erzeugte) myAPtr out-of-scope. Es wird der Destruktor des auto_ptr aufgerufen, der dann - automatisch - delete() auf den (anonymen) Pointer auf MyClass aufruft.

Zitat:
Und was passiert, wenn ein und dasselbe Objekt in 2 versch. Listen stehen soll? Dann zeigen doch die jeweiligen anonymen Autopointer auf das gleiche Objekt, es kann aber nur einmal freigegeben werden!?

Nein. auto_ptr und refernziertes Objekt stehen in einer 1:1-Beziehung. Auf ein Objekt kann immer nur ein auto_ptr zeigen. In diesem Fall ist auto_ptr die falsche Lösung.

Wenn Du tatsächlich ein- und dasselbe Objekt in zwei Listen brauchst, brauchst Du tatsächlich einen Reference Counter in irgendeiner Form.

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 13:19 Uhr

Reth
Posts: 1812
Nutzer
@Solar

Danke nochmals für die neuen Infos!

[quote]
Original von Solar:
Zitat:
Zitat:
Und was passiert, wenn ein und dasselbe Objekt in 2 versch. Listen stehen soll? Dann zeigen doch die jeweiligen anonymen Autopointer auf das gleiche Objekt, es kann aber nur einmal freigegeben werden!?

Nein. auto_ptr und refernziertes Objekt stehen in einer 1:1-Beziehung. Auf ein Objekt kann immer nur ein auto_ptr zeigen. In diesem Fall ist auto_ptr die falsche Lösung.

Wenn Du tatsächlich ein- und dasselbe Objekt in zwei Listen brauchst, brauchst Du tatsächlich einen Reference Counter in irgendeiner Form.


Gibts dafür ne standardisierte oder zumindest übliche Vorgehensweise in C++?

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 14:12 Uhr

Solar
Posts: 3674
Nutzer
Har-har... das hab' ich jetzt davon... :glow: I-)

Vergiß, was ich über auto_ptr geschrieben hab... bzw. einen Teil davon: auto_ptr hat keinen Kopierkonstruktor, und kann als solches nicht in Container gespeichert werden... :glow: Da siehst Du mal, wie wenig ich mit new() und Pointern arbeite, daß mir solche Patzer unterlaufen...

Für den ambitionierten Pointer-Jongleur bleibt nur noch der shared_ptr - nur ist der leider (noch) nicht in der Standardbibliothek, sondern (noch) nur in der Boost-Bibliothek. Ansonsten bleibt nur selber implementieren... (Was den Vorteil hat, daß das auch mit Deinem jetzigen Compiler geht. ;) )

Also "Standard" ist leider nicht, und "üblich"... hängt halt zu sehr von Deiner Datenstruktur ab, und was Du eigentlich vorhast...

[ Dieser Beitrag wurde von Solar am 25.10.2004 editiert. ]

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 15:41 Uhr

Holger
Posts: 8090
Nutzer
Zitat:
Original von Solar:
Zitat:
Zitat:
Zunächst einmal, ganz wichtig, begreife C++ als eigene Sprache, nicht als eine andere Möglichkeit Java zu programmieren.

Das wird schwierig genug :glow:


Bitte, bitte gib Dir Mühe. Ich habe mir ein Projekt an's Bein gebunden, bei dem ein Java-Progger genau das gemacht hat: In C++ Java zu programmieren. Grau-en-haft. Ich krieg' jedesmal Migräne, wenn ich versuche, den Code wieder ein Stück lesbarer zu machen. 8)

Das ist umgekehrt leider nicht anders.
Vielleicht wäre es besser gewesen, wenn man Java eine von Grund auf anders aussehende Syntax verpaßt hätte. Aber ob es dann auch so erfolgreich geworden wäre...

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 15:48 Uhr

Holger
Posts: 8090
Nutzer
Zitat:
Original von Reth:
Gibts dafür ne standardisierte oder zumindest übliche Vorgehensweise in C++?

Solange Du MaxonC++ oder StormC 3 benutzt, ist die Frage nicht wirklich sinnvoll. Die heute empfehlenswerten Vorgehensweisen werden so gut wie immer Techniken benutzen, die zum Zeitpunkt, als diese Compiler aktuell waren, teilweise noch nicht einmal vollständig standardisiert waren, von der Implementierung im Compiler ganz zu schweigen.
Nicht umsonst lautet die Empfehlung für vollständiges C++ auf dem Amiga nicht einfach nur gcc, sondern aktueller gcc.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 16:17 Uhr

Reth
Posts: 1812
Nutzer
@Holger

Auch Dank für die Antworten!

Zitat:
Original von Solar:

Für den ambitionierten Pointer-Jongleur bleibt nur noch der shared_ptr - nur ist der leider (noch) nicht in der Standardbibliothek, sondern (noch) nur in der Boost-Bibliothek.


Also ist der C++ Programmierer gar kein so Pointer-Jongleur wie der C-Programmierer? Benutzt der C++ Programmierer fast nur Objekte und deren Kopien als viel mehr Referenzen auf Objekte (in Java hat man nur Referenzen)? Verbrät das nicht viel mehr Speicher als nötig wäre?

Zitat:
Also "Standard" ist leider nicht, und "üblich"... hängt halt zu sehr von Deiner Datenstruktur ab, und was Du eigentlich vorhast...

Eigentlich unabhängig von der Objektstruktur wenn ich mehr als eine Referenz auf das Objekt habe, dann muss ich in dem Fall selber zusehen, wie ich das sauber freigebe, so dass keine unzulässigen Aufrufe entstehen.
In meinem Fall hab ich mir halt selbst ein Bein gestellt, weil ich clever sein wollte und meine Listen bei ihrer Zerstörung alle Elemente versuchen freizugeben. Das geht natürlich nicht, wenn ein Element in mehr als einer Liste vorkommt.

Entweder ich ändere das ganze Konzept der Liste, oder ich verwende den Vorschlag von whooha und implementiere einen Counter, der beim Einfügen in die Liste erhöht und beim löschen, rausnehmen etc. erniedrigt wird. Auf den wird dann beim Zerstören der Liste geprüft, wenn er 0 ist, kann das Element ebenfalls zerstört werden.



[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 16:40 Uhr

gni
Posts: 1106
Nutzer
Zitat:
Reth:
Also ist der C++ Programmierer gar kein so Pointer-Jongleur wie der C-Programmierer? Benutzt der C++ Programmierer fast nur Objekte und deren Kopien als viel mehr Referenzen auf Objekte (in Java hat man nur Referenzen)?

Pauschal läßt sich das nicht beantworten. new() braucht (fast ;-) immer ein delete() und wie Du gesehen hast, gibt es starke Aversionen gegen new().
Zitat:
Verbrät das nicht viel mehr Speicher als nötig wäre?
Wieso? Auch statische Objekte belegen Platz.

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 17:17 Uhr

Solar
Posts: 3674
Nutzer
Zitat:
Original von Reth:

Also ist der C++ Programmierer gar kein so Pointer-Jongleur wie der C-Programmierer?


Korrekt. Zumal mit dem syntaktischen Element der Referenz ein guter Weg dazugekommen ist, Funktionen by reference aufzurufen (also ohne ein Objekt zu kopieren), ohne mit (fehleranfälligen) Pointern hantieren zu müssen.

Zitat:
Benutzt der C++ Programmierer fast nur Objekte und deren Kopien als viel mehr Referenzen auf Objekte (in Java hat man nur Referenzen)?

Wie gesagt, in C++ benutzt man viel und gerne die Referenz:

void foo( Object const & paramter )
{ ... }

In der Funktion ist "parameter" somit keine Kopie des Arguments, sondern das Argument selbst. Und wenn man das 'const' wegläßt, kann man "parameter" in der Funktion sogar verändern.

Zitat:
Verbrät das nicht viel mehr Speicher als nötig wäre?

Das Speicherprofil der durchschnittlichen C++-Anwendung liegt eher unter dem einer Java-Anwendung vergleichbarer Güte, weil Garbage Collection und der dazugehörige Aufwand wegfällt. (Von der Virtual Machine und dem JIT mal ganz zu schweigen.)

Zitat:
Entweder ich ändere das ganze Konzept der Liste, oder ich verwende den Vorschlag von whooha und implementiere einen Counter, der beim Einfügen in die Liste erhöht und beim löschen, rausnehmen etc. erniedrigt wird.

Ich kann mir immer noch nicht vorstellen, warum Du zwei Listen mit Referenzen auf dieselben Objekte haben willst, und Du hast mit keinem Wort etwas über die Natur dieser Listen erwähnt. Geht's hier nur um's Prinzip? Oder kann man evtl. an Deinem Design etwas ändern?

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 19:45 Uhr

Reth
Posts: 1812
Nutzer
Hi Solar,

Zitat:
Original von Solar:

Korrekt. Zumal mit dem syntaktischen Element der Referenz ein guter Weg dazugekommen ist, Funktionen by reference aufzurufen (also ohne ein Objekt zu kopieren), ohne mit (fehleranfälligen) Pointern hantieren zu müssen.

Wie gesagt, in C++ benutzt man viel und gerne die Referenz:

void foo( Object const & paramter )
{ ... }

In der Funktion ist "parameter" somit keine Kopie des Arguments, sondern das Argument selbst. Und wenn man das 'const' wegläßt, kann man "parameter" in der Funktion sogar verändern.


Ok I see.

Zitat:
Zitat:
Verbrät das nicht viel mehr Speicher als nötig wäre?

Das Speicherprofil der durchschnittlichen C++-Anwendung liegt eher unter dem einer Java-Anwendung vergleichbarer Güte, weil Garbage Collection und der dazugehörige Aufwand wegfällt. (Von der Virtual Machine und dem JIT mal ganz zu schweigen.)


Ich meinte ja auch, wenn man immer Objektkopien anlegt anstelle Zeiger auf die Objekte.

Zitat:
Ich kann mir immer noch nicht vorstellen, warum Du zwei Listen mit Referenzen auf dieselben Objekte haben willst, und Du hast mit keinem Wort etwas über die Natur dieser Listen erwähnt. Geht's hier nur um's Prinzip? Oder kann man evtl. an Deinem Design etwas ändern?

An dem Design kann und muss ich wohl was ändern. Sehe ein, dass es gar nicht nett von mir ist, der armen Liste das Löschen (Destruktoraufruf) ihrer Elemente zu überlassen, die ich anlege und zuweise. Die Liste kann ja gar nicht wissen, ob sie die Einzige ist, die ne Referenz auf das jeweilige Objekt hat!

Es geht hier eigentlich schon ums Prinzip. Habe auch auf Arbeit öfters mal den Fall, dass eine Objekt an mehreren Stellen auftaucht und auch in verschiedenen Behältern wie Listen. Das ist nicht immer ganz unproblematisch aber in dem genannten Fall hier mit meiner Freigabe im Listendestruktor ist es ein Designfehler.

Ciao

[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 19:47 Uhr

Reth
Posts: 1812
Nutzer
Zitat:
Original von gni:
Zitat:
Reth:
Also ist der C++ Programmierer gar kein so Pointer-Jongleur wie der C-Programmierer? Benutzt der C++ Programmierer fast nur Objekte und deren Kopien als viel mehr Referenzen auf Objekte (in Java hat man nur Referenzen)?

Pauschal läßt sich das nicht beantworten. new() braucht (fast ;-) immer ein delete() und wie Du gesehen hast, gibt es starke Aversionen gegen new().
Zitat:
Verbrät das nicht viel mehr Speicher als nötig wäre?
Wieso? Auch statische Objekte belegen Platz.

Ja das tun sie. Aber ich meinte ja, dass Zeiger auf Objekte weniger Speicherplatz verbrauchen, als komplette Objekte, die immer wieder kopiert/geklont werden. Daher meine Frage.


[ - Antworten - Zitieren - Direktlink - ]

25.10.2004, 20:08 Uhr

Holger
Posts: 8090
Nutzer
Zitat:
Original von Reth:
Zitat:
Das Speicherprofil der durchschnittlichen C++-Anwendung liegt eher unter dem einer Java-Anwendung vergleichbarer Güte, weil Garbage Collection und der dazugehörige Aufwand wegfällt. (Von der Virtual Machine und dem JIT mal ganz zu schweigen.)
Ich meinte ja auch, wenn man immer Objektkopien anlegt anstelle Zeiger auf die Objekte.
Trotzdem.
Wie Solar schon gesagt hat, benutzt man für größere Objekte Referenzen. Bei kleineren Objekten kann der Performance-Verlust durch die Indirektion größer sein, als das Kopieren von ein paar bytes mehr (passiert ohnehin nur im L1-Cache). Da das Ganze in einem StackFrame passiert, der ohnehin in 90% aller Codestellen größer als der gerade verwendete Platz ist, siehst Du keinen höheren Verbrauch.
Bei aktuellen Desktop-JavaVM's, die sich auf maximale Performance konzentrieren, wird höherer Speicherverbrauch in Kauf genommen, deshalb passiert dort (fast) das Gleiche, nur daß der Programmierer davon nichts mitbekommt (und auch keine Kontrolle darüber hat). Je nach Lebensdauer befindet sich das Objekt in einem Speicherbereich, den man mit dem klassischen C-Stack oder dem C-Heap vergleichen kann. Und wenn die Einstufung sich ändert, wird _kopiert_.

mfg
--
Good coders do not comment. What was hard to write should be hard to read too.

[ Dieser Beitrag wurde von Holger am 25.10.2004 editiert. ]

[ - Antworten - Zitieren - Direktlink - ]

27.10.2004, 08:14 Uhr

Reth
Posts: 1812
Nutzer
Nochmal vielen Dank,

sehe nun schon etwas klarer.
Aber worin genau besteht in C++ der Unterschied zwischen einer Referenz und einem Pointer auf ein Objekt?

Habe bei mir gestern folgendes Problem festgestellt, dass genau damit zusammenhängt:
Meine Liste arbeitet noch mit Zeigern auf Objekte. Wenn ich nun dem Objekt, welches die Liste enthält ein Inhaltsobjekt für diese Liste gebe, gebe ich dieses nun wie Solar vorgeschlagen hat:

a.addElement(B("CD", 12, TRUE)); // nur als Syntaxbeispiel

Im Objekt a in der Methode addElement mache ich nun folgendes, da meine Liste noch nicht auf Referenzen umgestellt ist (was ich aber als nächstes tun werde!):

addElement(B &b)
{
list.addElement(&b);
}

Damit habe ich einen Zeiger auf die Referenz von b.
Kann es nun sein, dass nach Verlassen der addElement-Methode der Zeiger auf die Referenz nichts zählt und deswegen der Destruktor von B gerufen wird (so gestern bei mir geschehen)?

Ich hoffe, dass dies nicht mehr der Fall ist, wenn meine Liste auf Referenzen umgestellt ist und ich in a folgendes schreiben kann:

addElement(B &b)
{
list.addElement(b);
}

Oder ist dort nach dem Verlassen dieser Methode wieder damit zu rechnen, dass direkt im Anschluss der Destruktor von B gerufen wird? Das stünde dann im Widerspruch zu dem was Solar bzgl. Liste und Referenz darin schon sagte und würde dann wohl am Alter meines Compilers liegen.

Ciao

[ - Antworten - Zitieren - Direktlink - ]

27.10.2004, 09:51 Uhr

Solar
Posts: 3674
Nutzer
Zitat:
Original von Reth:

Aber worin genau besteht in C++ der Unterschied zwischen einer Referenz und einem Pointer auf ein Objekt?


Ein Pointer kann auf ein beliebiges Objekt zeigen, und von einem auf ein anderes Objekt "umgebogen" werden. Die Syntax unterscheidet sich von einem "echten" Objekt dadurch, daß ein Pointer dereferenziert werden muß. Ein Pointer ist eine eigenständige Datenstruktur.

Eine Referenz zeigt auf ein ganz spezielles Objekt, kann nicht umgebogen werden, und unterscheidet sich syntaktisch nicht von einem "echten" Objekt. Eine Referenz ist nur ein anderer Variablenname für dieselbe Datenstruktur (das Objekt) - also ein Alias.

Zitat:
Im Objekt a in der Methode addElement mache ich nun folgendes, da meine Liste noch nicht auf Referenzen umgestellt ist (was ich aber als nächstes tun werde!):

addElement(B &b)
{
list.addElement(&b);
}

Damit habe ich einen Zeiger auf die Referenz von b.


Nein, Du hast einen Zeiger auf b. ;)

Nebenbei kannst Du in einer Liste keine Referenzen speichern, da es die Referenz nicht als "Datenobjekt" gibt. Entweder Du hast ein Objekt (oder eine Referenz darauf, was äquivalent ist), oder einen Pointer auf ein Objekt. ;)

Zitat:
Kann es nun sein, dass nach Verlassen der addElement-Methode der Zeiger auf die Referenz nichts zählt und deswegen der Destruktor von B gerufen wird (so gestern bei mir geschehen)?

C++ macht kein Reference Counting; der Pointer auf b hält b nicht am Leben, wenn b selbst out-of-scope geht.

Der Destruktor wird wahrscheinlich nicht beim Verlassen von addElement() aufgerufen, sondern beim Verlassen der aufrufenden Funktion, in der b als lokale Variable deklariert wurde. ;)

Zitat:
Ich hoffe, dass dies nicht mehr der Fall ist, wenn meine Liste auf Referenzen umgestellt ist und ich in a folgendes schreiben kann:

addElement(B &b)
{
list.addElement(b);
}

Oder ist dort nach dem Verlassen dieser Methode wieder damit zu rechnen, dass direkt im Anschluss der Destruktor von B gerufen wird?


Wenn sich im Aufrufer nichts ändert, ja. Aber in list liegt ja dann eine Kopie von b, auch wenn das wahrscheinlich nicht das ist, was Du vorhattest. ;)



[ Dieser Beitrag wurde von Solar am 27.10.2004 editiert. ]

[ - Antworten - Zitieren - Direktlink - ]

27.10.2004, 10:21 Uhr

Reth
Posts: 1812
Nutzer
Hi nochmal,

[quote]
Original von Solar:
Zitat:
Ein Pointer kann auf ein beliebiges Objekt zeigen, und von einem auf ein anderes Objekt "umgebogen" werden. Die Syntax unterscheidet sich von einem "echten" Objekt dadurch, daß ein Pointer dereferenziert werden muß. Ein Pointer ist eine eigenständige Datenstruktur.

Eine Referenz zeigt auf ein ganz spezielles Objekt, kann nicht umgebogen werden, und unterscheidet sich syntaktisch nicht von einem "echten" Objekt. Eine Referenz ist nur ein anderer Variablenname für dieselbe Datenstruktur (das Objekt) - also ein Alias.


Danke. Hab ich verstanden. Sehr gut!

Zitat:
C++ macht kein Reference Counting; der Pointer auf b hält b nicht am Leben, wenn b selbst out-of-scope geht.

Der Destruktor wird wahrscheinlich nicht beim Verlassen von addElement() aufgerufen, sondern beim Verlassen der aufrufenden Funktion, in der b als lokale Variable deklariert wurde. ;)


Richtig, hab mich da falsch ausgerückt.

Zitat:
Zitat:
Ich hoffe, dass dies nicht mehr der Fall ist, wenn meine Liste auf Referenzen umgestellt ist und ich in a folgendes schreiben kann:

addElement(B &b)
{
list.addElement(b);
}

Oder ist dort nach dem Verlassen dieser Methode wieder damit zu rechnen, dass direkt im Anschluss der Destruktor von B gerufen wird?


Wenn sich im Aufrufer nichts ändert, ja. Aber in list liegt ja dann eine Kopie von b, auch wenn das wahrscheinlich nicht das ist, was Du vorhattest. ;)


Leider nein! :(

Aber wieso jetzt eine Kopie? Ich hab Referenz so verstanden, dass keine Kopie des Objektes angelegt wird? Jetzt bin ich verwirrt. Würde nämlich genau letzteres benötigen: Die Referenz auf ein Objekt in der Liste - aber keine Kopie des Objektes in der Liste. Denn wenn in einer weiteren Liste ebenfalls eine Referenz auf das gleiche Objekt gespeichert ist und sich das Objekt ändert, sollen beide Referenzen auf das geänderte Objekt verweisen.
Wenn jedoch nun bei Übergabe der Referenz an die Liste eine Kopie des Objektes angelegt wird ist das natürlich denkbar schlecht!

Was tut man in so einem Fall? Doch wieder auf Zeiger zurückfallen?

[ - Antworten - Zitieren - Direktlink - ]

27.10.2004, 10:43 Uhr

Solar
Posts: 3674
Nutzer
Zitat:
Original von Reth:

Aber wieso jetzt eine Kopie? Ich hab Referenz so verstanden, dass keine Kopie des Objektes angelegt wird? Jetzt bin ich verwirrt.


Na, entweder speichert Dein addElement() eine Kopie, oder einen Pointer auf das Original. Die Referenz ist keine Datenstruktur. ;)

Zitat:
Denn wenn in einer weiteren Liste ebenfalls eine Referenz auf das gleiche Objekt gespeichert ist und sich das Objekt ändert, sollen beide Referenzen auf das geänderte Objekt verweisen.

Dann mußt Du einen Pointer nehmen, entweder auf das Objekt selbst, oder eine Wrapper-Klasse, die das Reference Counting übernimmt.

Zitat:
Wenn jedoch nun bei Übergabe der Referenz an die Liste eine Kopie des Objektes angelegt wird ist das natürlich denkbar schlecht!

Nein, die Übergabe per Referenz legt keine Kopie an. Aber entweder speichert Deine Liste eine Sequenz von "Object" - das wären dann Kopien - oder "Pointer auf Object" - das wären dann Pointer.

Referenzen, nur um das nochmal zu betonen, sind keine Datenstruktur in sich, und auch kein Verweis auf ein Original, sondern nur ein weiterer Name für das Original selbst.

[ - Antworten - Zitieren - Direktlink - ]

27.10.2004, 12:27 Uhr

Reth
Posts: 1812
Nutzer
@Solar

Jetzt bin ich etwas durcheinander. In einem früheren Post hast Du doch gesagt:

Zitat:
Zitat:
Heisst das in meinem Fall, wenn ich eine Klasse A habe und ich diese mit

list.addElement(A("Param1", 2, 'C', TRUE));

in die Liste lege sie dort solange bleibt, bis die Liste zerstört wird, auch wenn die Methode und die Klasse, in der dieser Aufruf steht zwischenzeitlich verlassen wird?


Solange die Variable list besteht, ja.


Und nun:

Zitat:
Zitat:
Ich hoffe, dass dies nicht mehr der Fall ist, wenn meine Liste auf Referenzen umgestellt ist und ich in a folgendes schreiben kann:

addElement(B &b)
{
list.addElement(b);
}

Oder ist dort nach dem Verlassen dieser Methode wieder damit zu rechnen, dass direkt im Anschluss der Destruktor von B gerufen wird?


Wenn sich im Aufrufer nichts ändert, ja. Aber in list liegt ja dann eine Kopie von b, auch wenn das wahrscheinlich nicht das ist, was Du vorhattest.


Also das blick ich nicht so ganz. Widerspricht sich in meinen Augen etwas (liegt evtl. daran, dass ich mich mit diesen ganzen Konzepten noch nicht so gut auskenne)?

Meine Listen enthalten als Elemente immer nur Wrapperklassen, die den eigentlichen Inhalt speichern. Das Ganze funktioniert mit nem Template, damit ich alle Arten von Objekten mit einem Listentyp bearbeiten kann (wobei pro Listeninstanz nur jeweils eine Art von Objekten gespeichert wird).

Kann den dazugehörigen Source später mal posten!

[ - Antworten - Zitieren - Direktlink - ]

27.10.2004, 12:40 Uhr

Solar
Posts: 3674
Nutzer
Zitat:
Original von Reth:

Jetzt bin ich etwas durcheinander.


Ich weiß. Ist auch fies. ;)

Im ersten zitierten Posting bezog ich mich auf die Lebenszeit anonymer Objekte, wenn sie in einen Standardcontainer (wie z.B. vector) gepackt werden, denn dort werden Objektkopien gespeichert. Wenn Dein selbstgeschriebener Container Pointer auf die hinzugefügten Objekte speichert, dürfen diese Objekte natürlich nicht als anonyme, automatische Variablen erzeugt werden.

Zitat:
Meine Listen enthalten als Elemente immer nur Wrapperklassen, die den eigentlichen Inhalt speichern. Das Ganze funktioniert mit nem Template, damit ich alle Arten von Objekten mit einem Listentyp bearbeiten kann (wobei pro Listeninstanz nur jeweils eine Art von Objekten gespeichert wird).

Genauso wie vector<int>, vector<string> etc.

Geschickt programmiert brauchst Du keine Wrapperklassen; der Template-Mechanismus ist mächtig genug für solche Aufgaben.

Nur, es bleibt Fakt: Entweder speichert Dein Container Pointer oder Objekte.

Speichert er Objekte, können es nur Kopien der Originale sein (wie es die Standardcontainer machen). Diese existieren dann solange, wie der Container existiert.

Oder sie speichern Pointer (bzw. Kopien der Originalpointer, aber das führt zu weit), womit die Lebensdauer entweder ebenfalls der des Containers entspricht (Container-Destruktor ruft Element-Destruktoren), oder anderweitig erledigt werden.

Ich weiß, verwirrend...

[ - Antworten - Zitieren - Direktlink - ]

27.10.2004, 19:34 Uhr

Reth
Posts: 1812
Nutzer
So hier nun mal der Klartext!

Meine Elementklasse:


template <class T>
class ElementC
{
public:
ElementC(T *content)
{
this->content = content;
this->next = NULL;
};

~ElementC()
{
if (content != NULL)
{
delete content;
content = NULL;
}

if (next != NULL)
{
delete next;
next = NULL;
}
};

ElementC *getNext()
{
return next;
};

void setNext(ElementC *next)
{
this->next = next;
};

T *getContent()
{
return content;
};

private:
ElementC *next;
T *content;
};


Diese wird nun über folgende Methode der Liste hinzugefügt:

template <class T> void LinkedListC<T>::addElement(T *elem)
...
ElementC *root = new ElementC(elem);


Wie mach ich das nun am geschicktesten, dass meine Objekte in den Listen verweilen ihre Destruktoren erst beim Auflösen der jew. Liste gerufen werden (am besten automatisch) und keine Kopien angelegt werden, denn wenn ein Objekt in mehreren Listen ist, dann muss es immer das gleiche sein? :dance3:

Oder ist das jetzt zuviel gefragt, weil ich eigentlich selbst drauf kommen müsste? :glow:


Ach ja und wie ruf ich denn einen Destruktor auf ne Variable einer Templateklasse auf?
Also ich hab in der Definition meiner Klasse im Headerfile folgende stehen:


LinkedListC<FramesC> allFrames;


Wie ruf ich denn nun den Destruktor von allFrames syntaktisch korrekt auf? Dieser wird beim Beenden und verlassen der Klasse momentan noch nicht gerufen!

[ Dieser Beitrag wurde von Reth am 27.10.2004 editiert. ]

[ - Antworten - Zitieren - Direktlink - ]

28.10.2004, 11:54 Uhr

Solar
Posts: 3674
Nutzer
Also, wir haben einen Container (LinkedListC), eine Wrapperklasse (ElementC), und per new() angelegte Objects, von denen Pointer (eingepackt in die Wrapperklasse) in mehreren Containern abgelegt werden sollen. (Wären's keine Pointer, wären es Kopien, und das soll ja nicht.)

Beim Auflösen des letzten Containers soll delete() für die enthaltenen Objects aufgerufen werden.

D.h., "jemand" muß wissen, wieviele Wrapperklassen auf ein und dasselbe Object zeigen, um - wenn die letzte Wrapperklasse eines Objects destruiert wird - delete() auf Object aufrufen.

Meine Idee - zugegebenermaßen ohne groß zu "designen" und recht spontan zusammengeschustert:

Du implementierst zwei Funktionen in Object, "void registerWrapper()" und "bool releaseWrapper()". Der Konstruktor von ElementC ruft registerWrapper(), was einen internen Zähler der Object-Instanz hochzählt ("ich werde von (X) Instanzen ElementC referenziert"). Der Destruktor von ElementC ruft releaseWrapper(), was den Zähler runterzählt und false zurückgibt - außer, wenn der Zähler auf Null gesetzt wurde. In diesem Fall führt der Destruktor von ElementC ein delete() auf das enthaltene Object aus.

Wenn nun Dein LinkedListC beim Auflösen schön brav die Destruktoren für alle enthaltenen ElementC aufruft, ist Dein Problem wahrscheinlich gelöst.

Das ist nicht wirklich schön, und vor allem nicht universell verwendbar, aber evtl. reicht es für Deine Zwecke.

[ - Antworten - Zitieren - Direktlink - ]

28.10.2004, 12:04 Uhr

whooha
Posts: 41
Nutzer
Zitat:
Meine Idee - ...
hey - das war meine Idee™ :D



[ - Antworten - Zitieren - Direktlink - ]

28.10.2004, 12:05 Uhr

Reth
Posts: 1812
Nutzer
@Solar

Danke für die Antwort. So nen ähnlichen Ansatz hab ich schon begonnen. Mein momentanes Problem ist aber das zweite in meinem letzten Post beschriebene.
Ich hab Objektvariablen LinkedListC<Typ> liste; im HeaderFile bei der Objektdefinition angelegt und deren Destruktoren werden beim Beenden des Programmes anscheinend nicht gerufen.

Alle Versuche diese Destruktoren manuell im Code zu rufen, schlugen bisher mit nem Compilerfehler fehl.

Kann (sollte) man in C++ solche Membervariablen dann gar nicht definieren, da man sie nicht freigeben kann?

[ - Antworten - Zitieren - Direktlink - ]


-1- 2 [ - Beitrag schreiben - ]


amiga-news.de Forum > Programmierung > C++ Problem: Objektfreigabe [ - Suche - Neue Beiträge - Registrieren - Login - ]


.
Impressum | Datenschutzerklärung | Netiquette | Werbung | Kontakt
Copyright © 1998-2022 by amiga-news.de - alle Rechte vorbehalten.
.