Objekt auf dem Heap oder Stack?
-
&&&&& schrieb:
Shade Of Mine schrieb:
heutzutage verwendet man starke garbage collectoren - die sind einem ref counting um dimensionen ueberlegen und genau das ist der punkt. ref counting ist eine art GC mit kaum vorteilen eines GCs aber eine menge nachteile.
...
aber alleine wenn es um multithreading geht, ist ref counting einfach nicht mehr verwendbar (weil du dauernd locken musst - das kostet so sinnlos viel zeit...)Aber wie machen dann diese starken GCs? Die müssen doch auch wissen ob ein Objekt noch verwendet wird. Wie kann man das machen, ohne die Referenzen darauf zu zählen?
Ein GC laeuft ueber Collections. Eine Collection ist, wenn der GC die anwendung einfriert und dann alle referenzen durchzaehlt.
das ist in der theorie furchtbar langsam - in der praxis optimiert man da aber etwas.
Es gibt verschiedene strategien. Die Haupstrategie bei modernen GCs ist meistens Generationen basierend. Alle Objekte werden in Generationen eingeteilt: die generation zero (also die 1.) ist voll von objekten die gerade erst erstellt wurden. bei einer gen zero collection werden nur die objekte angesehen die seit der letzten collection erstellt wurden. dadurch kann man enorm schnell sein, da die meisten objekte nicht beachtet werden. alle objekte die diese collection ueberleben, wandern eine generation nach hinten. desto hoeher die generation, desto teurer die collection.
der wirkliche vorteil eines GCs liegt aber darin, dass er das "big picture" sieht. er lernt wie deine anwendung funktioniert und optimiert sich dahingehend. wenn er merkt du erstellst wenige objekte - laeuft er sehr selten. wenn er merkt du erstellst viele kurzlebige objekte, verstaerkt er sich auf die generation zero.
das schoene dabei ist, ein GC haelt die fragmentierung des speichers stabil und da er lernt, laeuft das programm eher schneller desto laenger es laeuft - waehrend du in nicht GC sprachen bei langen laufzeiten oefters mit speicher fragmentierung zu kaempfen hast.
und ein GC kann dir den speicher natuerlich schneller besorgen, da er genau weiss wo welcher speicher im cache liegt
-
Dravere schrieb:
...
Simon2 schrieb:
... std::vector ...
Ich denke std::vector ist, wie ich schon mal gesagt habe, nicht vergleichbar. ...
Nunja, Dein Argument ("Der Benutzer könnte vergessen, ein passendes delete zum new zu machen") treifft aber auf std::vector 100%ig zu ... damit müsste auch Deine Schlussfolgerung ("Benutzerunfreundlich und memory-leak-gefährlich") zutreffen.
Dravere schrieb:
...
Naja, man setzt ja allgemein auch voraus, dass der Benutzer der Bibliothek, die dazugehörige Dokumentation liest ;)...Mit DEM Argument kannst Du Dir das Ganze erst Recht sparen. Dann schreibst Du halt in die Doku "Am Ende delete machen !" und fertig.
Da Du aber (was löblich ist) Deine Schnittstelle intuitiv designen möchtest, solltest Du es ernst nehmen, wenn ich sage, dass Du hier an der Intuition von einem sehr großen Teil zukünftiger Benutzer vorbei designst.Dravere schrieb:
...
c) Beide sind zuständig ... was passiert nun?...Wir haben die Erkenntnis, dass es sich um schlechtes Design handelt. :p
Dravere schrieb:
...
...Du hast ein Konstrukt, wo bereits ein Pointer festgesetzt wurde, von der Bibliothek. Nun kommt der Benutzer der Bibliothek und will diesen Pointer durch seinen eigenen ersetzen. Wer gibt nun den Speicher vom Objekt frei, welches ursprünglich von der Bibliothek kam?...Natürlich der Benutzer, der ja sein eigenes Objekt erzeugt und "in die Lib gehängt" hat.
Dies ist gar kein Beispiel für "gemischte Verantwortung", sondern eines, wo man offensichtlich 2 Objekte mit unterschiedlicher Verantwortlichkeit hat.
(Die Lib könnte ihr Objekt zerstören, wenn es durch eines vom Benutzer ersetzt werden soll).Dravere schrieb:
...
Simon2 schrieb:
BTW. Was spricht denn gegen Folgendes Modell: ...
Ich will ja nur den Zeiger einer ganz bestimmten Klasse setzen. Da braucht es keine templates! ...
Umso besser ! Dann mach es doch ohne template.
Dravere schrieb:
...
Zudem müsste ich noch eine Möglichkeit einbauen, den pointer auf NULL zu setzen. Noch eine Methode zusätzlich, welche noch ein Reset durchführt....Hmmm, das glaube ich nicht. Dein Objekt braucht nur EINE Methode, die das tut, was es fachlich bedeutet, wenn der Zeiger "auf NULL gesetzt" wird.
Dravere schrieb:
...
Zudem wird da auch nur auf dem Heap allokiert
...Muss nicht. Es ging mir nur darum, die Verantwortung in die Lib zu verlegen. Sie kann (gerade DANN) problemlos auch Stackobjekte verwenden.
Dravere schrieb:
...
Und nicht zuletzt, kann man keine Objekte erzeugen, welche direkt über den Konstruktor die richtigen Werte zugewiesen bekommen....Natürlich kann man das. Dass meine "set-Funktion" keine Parameter entgegennimmt, ist natürlich nur ein Beispiel.
Natürlich kannst Du mit "Referenzzählung" eine Technik einsetzen, die dein momentanes technisches Problem ("double-delete") löst. Aber was da fachlich dahintersteckt, wenn "viele Köche im selben Brei rühren", wirst Du fachlich klären müssen.... und da kommt man schnell mal in Teufels Küche.
Mir scheint alllerdings, dass Du mit dieser DIskussion schon abgeschlossen und Dich auf smart_pointer/Referenzzählung eingeschossen hast. Dann wünsche ich Dir viel Erfolg (ganz un-ironisch !).Gruß,
Simon2.
-
Shade Of Mine schrieb:
...
Du versuchst Java in C++ zu programmieren. Das kann nicht klappen.
...Das bringt es schön auf den Punkt.
Gruß,
Simon2.
-
Shade of Mine schrieb:
deshalb ein kleiner tip:
programmiere in C++ wie ein C++ programmierer, in Java wie ein Java programmierer und in python wie ein python programmierer."Mir käme nicht in den Sinn, in C++ wie in Java zu programmieren. Nur fällt mir halt keine bessere Lösung ein, bzw. fiel mir ein und ich habe immer noch mühe ^^
Simon2 schrieb:
Mir scheint alllerdings, dass Du mit dieser DIskussion schon abgeschlossen und Dich auf smart_pointer/Referenzzählung eingeschossen hast.
Wenn dem so wäre, dann hätte ich schon lange gesagt, dass man die Diskussion schliessen kann und vor allem hätte ich inzwischen weiterprogrammiert, was aber nicht der Fall ist
Mein Problem ist derzeit, mir geht einfach nicht das Licht auf, wie ihr das Problem lösen würdet. Ihr sagt zwar "Design Fehler", aber eine wirkliche Lösung sehe ich nicht ^^
Daher schnell die Frage/das Problem, so kurz wie möglich zusammengefasst:
Wie kann ich bei einem Objekt der Klasse B, ein optionales Objekt der Klasse A setzen, so dass jeweils derjenige die Speicherverwaltung übernimmt, welcher das Objekt gesetzt hat?Mir ist einfach nicht klar, wie ich eine von der Bibliothek erstellten Baumstruktur mit optionalen "Elementen", dem Benutzer der Bibliothek übergeben kann, so dass die Biliothek sich dann immer noch um den womöglich allozierten Speichern kümmert, welcher durch Speicher, bzw. NULL-Pointern, vom Benutzer ersetzt wird, ohne dass im nachhinein dann, der Speicher des Benutzers auch berührt wird.
<- really confused
Grüssli
-
Dravere schrieb:
Mir ist einfach nicht klar, wie ich eine von der Bibliothek erstellten Baumstruktur mit optionalen "Elementen", dem Benutzer der Bibliothek übergeben kann, so dass die Biliothek sich dann immer noch um den womöglich allozierten Speichern kümmert, welcher durch Speicher, bzw. NULL-Pointern, vom Benutzer ersetzt wird, ohne dass im nachhinein dann, der Speicher des Benutzers auch berührt wird.
Beschreib einfach mal genauer was du willst. Die Baumstruktur koennte zB den Ownership selber uebernehmen. Du koenntest es ueber Templates loesen und somit garnicht erstmal eine dynamische Allokierung provozieren.
scoped_ptr/auto_ptr bieten sich auch immer an wenn es um owner ship transfer geht.
Uebrigens braucht man Zeiger/Referenzen nur dann wenn man laufzeit polymorphie verwendet - ansonsten kannst du immer kopien ziehen.
Schau dir auch mal Libraries an die aehnlich zu deiner sind. uU die C++ streams (auch wenn sie nicht die schoensten sind): du kannst bei einem stream dynamisch den buffer bestimmen.
-
Shade Of Mine schrieb:
Uebrigens braucht man Zeiger/Referenzen nur dann wenn man laufzeit polymorphie verwendet - ansonsten kannst du immer kopien ziehen.
Naja, bei Baumstrukturen oder verketteten Listen ja nicht (oder?:)).
Dravere schrieb:
Mir ist einfach nicht klar, wie ich eine von der Bibliothek erstellten Baumstruktur mit optionalen "Elementen", dem Benutzer der Bibliothek übergeben kann, so dass die Biliothek sich dann immer noch um den womöglich allozierten Speichern kümmert, welcher durch Speicher, bzw. NULL-Pointern, vom Benutzer ersetzt wird, ohne dass im nachhinein dann, der Speicher des Benutzers auch berührt wird.
Wie Shade schon meinte, eine gute Möglichkeit wäre es, dem Baum zu überlassen, wie er seinen Speicher verwaltet. Also im Prinzip so (um konkret zu sein):
class TreeNode { public: void setLeft( const string& v ) { if ( left != NULL ) // Kaputt machen left = // Allokieren left->val = v; } private: string val; TreeNode *left, *right; };
Das hat auch den großen Vorteil, dass du im Baum einen eigenen Speichermanager einbauen kannst. Wenn's z.B. ein spezieller Baum ist, der eigentlich immer ziemlich groß wird, kannst du im Vorhinein schon 1 MB (oder so) an Speicher holen und die Nodes/Knoten mit "placement new" auf diesem Speicher unterbringen; so musst du nicht so oft Speicher anfordern.
edit2: Und das läuft auch unter dem OOP-Prinzip des Information-versteckens: Von außen ist es dir völlig egal, wie der Baum den Speicher verwaltet, er macht's selbst und du brauchst dich nicht drum zu kümmern. Und wenn der Programmierer des Baums sich entscheidet, einen (anderen) Speichermanager einzubauen, bekommst du davon genau null mit
edit3: Shade, hättest du vielleicht Lust, einen Artikel über GCen für's Magazin zu schreiben? Das Thema ist auf jeden Fall hochinteressant!
-
Badestrand schrieb:
Shade Of Mine schrieb:
Uebrigens braucht man Zeiger/Referenzen nur dann wenn man laufzeit polymorphie verwendet - ansonsten kannst du immer kopien ziehen.
Naja, bei Baumstrukturen oder verketteten Listen ja nicht (oder?:)).
Warum nicht?
std::list<std::string> l; l.push_back(std::string("test"));
klappt wunderbar
edit3: Shade, hättest du vielleicht Lust, einen Artikel über GCen für's Magazin zu schreiben? Das Thema ist auf jeden Fall hochinteressant!
gib mir zeit und ich machs :p aber gregor und optimizer und die ganzen kollegen wissen da dimensionen mehr als ich... frag die mal.
-
So, ich glaub mir ist ein Licht aufgegangen und habe mal was kleines auf Papier gekritzelt. Eigentlich bin ich nun einfach von der Idee von Simon2 ausgegangen. So erscheint es mir, zumindest jetzt, tatsächlich deutlich benutzerfreundlicher:
class CMetaDataXYZ { // Daten in der grösse von ca. 4 Bytes. // Konstruktoren und all das Zeug. }; class CMetaDataABC { // Attributes // private: unsigned char m_uc1; unsigned short m_us1; CMetaDataXYZ* m_pMetaDataXYZ; // Constructors & Destructor // public: CMetaDataABC(unsigned char uc1, unsigned short us1, CMetaDataXYZ* pMetaDataXYZ) : m_uc1(uc1) , m_us1(us1) , m_pMetaDataXYZ(pMetaDataXYZ ? new CMetaDataXYZ(*pMetaDataXYZ) : NULL) { } ~CMetaDataABC() { delete m_pMetaDataXYZ; }; // Methods // public: const CMetaDataXYZ* get_metadataxyz() const { return m_pMetaDataXYZ; }; void set_metadataxyz(const CMetaDataXYZ* pMetaDataXYZ) { delete m_pMetaDataXYZ; m_pMetaDataXYZ = (pMetaDataXYZ ? new CMetaDataXYZ(*pMetaDataXYZ) : NULL); } // Weitere getter und setter };
Das wäre dann wohl besser, oder?
Es wird zwar immer noch intern der Heap benutzt. Aber über ein Template-Parameter für einen Allokator könnte man zusätzliche Funktionalität dem Benutzer übergeben, korrekt?@Shade Of Mine,
Ich glaube Badestrand meinte die Verlinkung zwischen den Listenelementen. Die werden intern wohl auch Zeiger oder Referenzen sein.Und trommele mal Gregor und Optimizer zusammen. Ihr kennt euch so gut in GCs aus, dann wird es wohl möglich sein, dass eure GCs ein bisschen Zeit frei machen können, damit ihr über die GCs was schreiben könnt
Grüssli
-
du verlierst hier Laufzeitpolymorphie - warum also nicht auf new/delete verzichten?
-
Shade Of Mine schrieb:
du verlierst hier Laufzeitpolymorphie - warum also nicht auf new/delete verzichten?
1. Es gibt hier keine Polymorphie.
2. Wie sollte ich auf new/delete verzichten?Grüssli
-
Dravere schrieb:
Shade Of Mine schrieb:
du verlierst hier Laufzeitpolymorphie - warum also nicht auf new/delete verzichten?
1. Es gibt hier keine Polymorphie.
2. Wie sollte ich auf new/delete verzichten?Sorry, mein Fehler.
Hab übersehen dass es den Zustand "metaData existiert nicht" gibt.
Dann ist die Lösung gut.
-
Dravere schrieb:
Das wäre dann wohl besser, oder?
Es wird zwar immer noch intern der Heap benutzt. Aber über ein Template-Parameter für einen Allokator könnte man zusätzliche Funktionalität dem Benutzer übergeben, korrekt?Absolut korrekt
Kleine eventuelle Stilverbesserung: Du könntest auch eventuell mit Referenzen arbeiten, z.B. so:
class CMetaDataABC { ... CMetaDataABC(unsigned char uc1, unsigned short us1) : m_uc1(uc1) , m_us1(us1) , m_pMetaDataXYZ(NULL) { } CMetaDataABC(unsigned char uc1, unsigned short us1, const CMetaDataXYZ& pMetaDataXYZ) : m_uc1(uc1) , m_us1(us1) , m_pMetaDataXYZ( new CMetaDataXYZ(pMetaDataXYZ) ) { } ~CMetaDataABC() { delete m_pMetaDataXYZ; }; // Methods // public: // Und statt dem hier: const CMetaDataXYZ* get_metadataxyz() const { return m_pMetaDataXYZ; }; // Eventuell (Geschmackssache, will es nur als Möglichkeit in den Raum stellen): bool has_metadataxyz() const { return m_pMetaDataXYZ!=NULL; } const CMetaDataXYZ& get_metadataxyz() const { assert(m_pMetaDataXYZ); return *m_pMetaDataXYZ; } // Denn in den meisten Fällen ruft man wahrscheinlich eh erst "if (get_meta()!=NULL)" auf, bevor man // mit "get_meta()->bla()" drauf zugreift :) // Und statt: void set_metadataxyz(const CMetaDataXYZ* pMetaDataXYZ) { delete m_pMetaDataXYZ; m_pMetaDataXYZ = (pMetaDataXYZ ? new CMetaDataXYZ(*pMetaDataXYZ) : NULL); } // Eventuell (wieder Geschmackssache, Raum stellen und so): void clear_metadataxyz() { delete m_pMetaDataXYZ; m_pMetaDataXYZ=NULL; } void set_metadataxyz( const CMetaDataXYZ& meta ) { clear_metadataxyz(); m_pMetaDataXYZ=new CMetaDataXYZ(meta); } };
-
Versuch doch mal folgende Methode:
bool isOnStack(void *x){ int a; return (void*)&a < x; }
Unter der Annahme das der Anfang des Heaps immer hinter dem maximalen Bereich des Stacks liegt (und du keine Threads verwendest), dürfte das funktionieren.
EDIT:
Aber wenn du den Nutzern solche Einschränkungen aufbrummst, ist das nicht gerade "nett". Vor allem wenn er von diesen nichts weiß.
-
@Badestrand,
Das war das erste, was ich gemacht habe. Bin mir aber noch nicht sicher, welches besser ist, muss das noch genau durchdenken.Das schöne an meinem oben geschriebenen Code ist, das eigentlich das gleiche, was du mit vielen Funktionen machst, bei mir auch mit weniger geht.
// get_metadataxyz() liefert einen den Zeiger zurück // und abc ist ein Objekt von CMetaDataABC if(abc.get_metadataxyz()) { } // genau das gleiche, wie bei deiner Funktion if(abc.has_metadataxyz()) { } // Aber bei mir kann man auch das machen: const CMetaDataXYZ* pMDxyz = abc.get_metadataxyz(); if(!pMDxyz) { pMDxyz = new CMetaDataXYZ(); } // Mit pMDxyz arbeiten. // Das dürfte bei deine Lösung etwas schwerer werden
Naja, mal schauen
Danke für die Hilfe!
@Fellhuhn,
Lies den Thread durchGrüssli
-
Bei vier Seiten? Da poste ich doch lieber arrogant einfach rein.
-
Hi Dravere,
das ist die eine Möglichkeit (Lib hält Kopie).
Vorteil: Die Objekte sind entkoppelt (so kann z.B. der Anwender die Lib nicht durch ein vorzeitiges delete zu Fall bringen).
Nachteil: Die Objekte sind entkoppelt (Lib bekommt Änderungen am Anwenderobjekt nicht mit).Eine andere wäre tatsächlich der "Verantwortungsübergang":
class CMetaDataABC { // Attributes private: // ... bool m_myObj; public: CMetaDataABC(unsigned char uc1, unsigned short us1) : // ... , m_pMetaDataXYZ(new CMetaDataXYZ) , m_myObj(true) { } ~CMetaDataABC() { if(myObj) delete m_pMetaDataXYZ; }; // Methods // public: const CMetaDataXYZ* get_metadataxyz() const { return m_pMetaDataXYZ; }; void set_metadataxyz(const CMetaDataXYZ* pMetaDataXYZ) { if(myObj) delete m_pMetaDataXYZ; m_pMetaDataXYZ = pMetaDataXYZ; myObj = false;} // Weitere getter und setter };
Ist aber eine Designalternative, die nicht unbedingt besser/schlechter ist als die obige.... nur eben anders.
Gruß,
Simon2.
-
Fellhuhn schrieb:
Versuch doch mal folgende Methode:
bool isOnStack(void *x){ int a; return (void*)&a < x; }
Unter der Annahme das der Anfang des Heaps immer hinter dem maximalen Bereich des Stacks liegt (und du keine Threads verwendest), dürfte das funktionieren.
EDIT:
Aber wenn du den Nutzern solche Einschränkungen aufbrummst, ist das nicht gerade "nett". Vor allem wenn er von diesen nichts weiß.In der Praxis wird das zwar (mit deinen Einschränkungen) funktionieren, aber der Standard sagt ganz klar:
If two pointers p and q of the same type point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null, the results of p<q, p>q, p<=q, and p>=q are unspecified.
Felix
Außerdem wurde ja schon auf den anderen Seiten festgestellt, dass das Herausfinden, ob ein Objekt auf dem Stack, oder dem Heap liegt einfach schlechtes Design ist...