[erledigt] Abfrage vor delete: statisches oder dynamisches Objekt
-
Tag zusammen,
ich studiere Bachelor-Informatik im zweiten Semester. Ich versuche mich grad in virtueller Mehrfachvererbung. Nun stehe ich vor dem Problem, zwischen statischen und dynanmischen Objekten unterscheiden zu können.
Bisschen Code zur Erläuterung:
// die Klassen stehen in getrennten .h-Dateien // Vordeklaration zur Vermeidung von zirkulärem Include class Relation; class Base abstract { public: Base(); virtual ~Base(); //... virtual void set_relation(Relation*); // setter statt Base(...) wg. sich ändernder Relationen private: //... Relation *r; }; // abgeleitete Klassen wie zum Beispiel: class Derived1: virtual public Base { /* ... */ }; class Derived2: virtual public Base { /* ... */ }; class Derived12: virtual public Derived1, virtual public Derived2 { /* ... */ }; class Relation { public: //... Relation(...,Base*,Base*) // setzt b1, b2 und ruft set_relation(this) der Objekte auf private: //... Base* b1; Base* b2; };
Es gibt also die Base-Klasse, verschiedene Derived-Klassen und eine Relation-Klasse, um eine Beziehung zwischen den verschiedenen Derived-Klassen zu ermöglichen.
Im Hauptprogramm sieht es dann so aus:
#include <vector> #include "..." // die ganzen Klassen using namespace std; int main() { vector<Base*> b; b.push_back(new Derived1(...)); b.push_back(new Derived2(...)); b.push_back(new Derived12(...)); // etc. // diverse Relationen eingehen, zum Beispiel Relation *r1 = new Relation(...,b[0],b[1]); /* diverse Aufrufe */ // aufräumen if(r1) delete r1; for(unsigned int i = ..., ..., i--) { if(b[i]) delete b[i]; b.pop_back(); } return 0; }
Nun möchte ich aber auch erlauben, statische Objekte in den Vector zu geben.
Derived2 d2(...); b.push_back(&d2);
delete auf das statische Objekt beim Aufräumen ist natürlich Murks, aber: Wie fange ich sowas ab? Ich habe es mit typeof bzw. typeid probiert, was aber zu keinem Erfolg führte bisher. Leider brachte mich auch eine Suche im Forum bzw. per Google nicht weiter. Wie würdet ihr das lösen?
Ist übrigens keine Praktikumsaufgabe ^^ Ich probier einfach rum.
Und vielleicht noch eine allgemeine Frage: Ist mein Code, auch wenn ich nur das Nötigste hier geschrieben habe, soweit in Ordnung oder gäbe es Empfehlungen irgendwas anders zu machen? Bin ja noch relativ am Anfang, denke ich.
Danke schonmal fürs Feedback.
WTFs/Minute
-
Wie fange ich sowas ab? Ich habe es mit typeof bzw. typeid probiert, was aber zu keinem Erfolg führte bisher. Leider brachte mich auch eine Suche im Forum bzw. per Google nicht weiter. Wie würdet ihr das lösen?
Ich würde einfach keine Zeiger auf lokale Objekte in den std::vector stecken.
Wenn du das unbedingt willst/musst: speicher die Information mit ab ob das Objekt gelöscht werden soll/darf/muss.
Oder verwende einen Smart-Pointer der einen sog. "Deleter" mit abspeichern kann. z.B. boost::shared_ptr oder std::unique_ptr. Dann kannst du mit dem Deleter kontrollieren was beim "Löschen" des Objekts passieren soll.
-
Danke schonmal für den Hinweis auf die Smart-Pointer. Ich komme aber nicht weiter mit der Syntax - evtl. habe ich dich aber auch falsch verstanden.
vector<unique_ptr<Base*>> foo1; // geht foo1.push_back(new Derived1(...)); // geht nicht Derived2 d2(...) foo1.push_back(&d2); // geht auch nicht
Wenn ich nun folgendes mache, schmiert mir das Programm ab, nach return 0, weil wohl versucht wird, das statische Element zu löschen.
Derived1 d1(...) unique_ptr<Base> foo2(&d1);
Meine Fragen also:
Wie würde der korrekte Syntax aussehen und wie definiere ich den "Deleter" bzw. kann sein Verhalten steuern?
Habe schon gesehen, dass es die Methode get_deleter() gibt bzw. std::default_delete, aber was fange ich damit an? Finde leider keine passenden Beispiele dazu.
Danke schonmal!
-
Müsste inetwa so gehen (nicht getestet):
class Base; struct MyDeleter { public: explicit MyDeleter(bool doDelete) : m_doDelete(doDelete) void operator () (Base* b) const { if (m_doDelete) delete b; } private: bool m_doDelete; }; // Bei std::unique_ptr muss der Deleter-Typ als Template-Argument für unique_ptr selbst angegeben werden typedef unique_ptr<Base, MyDeleter> MyPtr; // nur Base, kein Base*, Zeiger ist implizit (ist ja ein smart-POINTER) vector<MyPtr> foo1; //foo1.push_back(new Derived1(...)); // geht nicht foo1.push_back(MyPtr(new Derived1(...), MyDeleter(true)); Derived2 d2(...); foo1.push_back(MyPtr(&d2, MyDeleter(false));
Mit std::shared_ptr geht es etwas eleganter, da man bei std::shared_ptr den Deleter erst bei der Erstellung des shared_ptr angeben muss (Deleter kann beliebigen Typ haben). Dafür hat shared_ptr deutlich mehr Overhead als unique_ptr.
-
Ich weiss nicht, ob es klug ist, besitzende Zeiger (auf dynamische Objekte) und reine Verweise (auf automatische Objekte) im gleichen Container zu speichern. Ein ähnliches Thema hatten wir übrigens vor Kurzem, wobei dort automatisch erkannt werden sollte, ob ein Speicher freizugeben ist oder nicht – was um Einiges schlimmer ist.
Wäre es nicht einfacher, entweder alles dynamisch anzufordern und eine Freigabe mit RAII einzurichten (dann bist du nämlich das Problem mit Gültigkeitsbereichen vom Stack los), oder nur passive Zeiger zu speichern, während die Speicherverwaltung dem Benutzer überlassen ist (der ja seinerseits RAII verwenden könnte)?
Vielleicht noch zwei Anmerkungen am Rande:
delete
kann mit Nullzeigern umgehen, du musst also nicht aufNULL
prüfen.- Automatischer und statischer Speicherbereich sind nicht das Gleiche.
-
hustbaer schrieb:
Müsste inetwa so gehen (nicht getestet):
Da hat nur ein {} gefehlt, ansonsten geht es - danke
Nexus schrieb:
Wäre es nicht einfacher, entweder alles dynamisch anzufordern und eine Freigabe mit RAII einzurichten (dann bist du nämlich das Problem mit Gültigkeitsbereichen vom Stack los), oder nur passive Zeiger zu speichern, während die Speicherverwaltung dem Benutzer überlassen ist (der ja seinerseits RAII verwenden könnte)?
Musste grad erstmal den Begriff RAII nachschlagen - im Studium bisher nie gehört. Was ist mit einem passiven Zeiger gemeint?
Kann sein, dass es einfacher ist - bin wie geschrieben durch Rumprobieren auf das Problem gestoßen. Bin noch nicht so tief drin in der Materie.
delete prüft auf NULL? Gut zu wissen ^^ Und den Unterschied zw. den Speicherbereichen kenne ich. Ging mir nur drum, wie ich das Abfangen kann bei einem delete, dass nicht etwas gelöscht wird, was nicht gelöscht werden darf auf diese Weise.
-
WTFs/Minute schrieb:
Musste grad erstmal den Begriff RAII nachschlagen - im Studium bisher nie gehört.
Kurz gesagt bedeutet RAII, dass man Klassen hat, die Ressourcen (darunter Speicher) am Ende der Benutzung automatisch freigeben. Das hat enorme Vorteile im Gegensatz zur manuellen Verwaltung.
WTFs/Minute schrieb:
Was ist mit einem passiven Zeiger gemeint?
Ein Zeiger, der nicht besitzend ist, also keine Speicherverwaltungsfunktion übernimmt, sondern lediglich als Verweis dient.
int i = 7; int* p = new int; // besitzend (man muss den int über p freigeben) int* q = &i; // nicht-besitzend
-
Soweit erstmal Danke
Noch eine Nachfrage: Gibt es eine bessere Lösung als mein Versuch über die Klasse Relation eine Beziehung zwischen den Klassen herzustellen?
Und: Wäre es besser mit Referenzen statt mit Pointern zu arbeiten?
class Relation public: // statt Relation(...,Base*,Base*); // besser Relation(...,Base&,Base&); // ? };
-
WTFs/Minute schrieb:
Noch eine Nachfrage: Gibt es eine bessere Lösung als mein Versuch über die Klasse Relation eine Beziehung zwischen den Klassen herzustellen?
Was sind denn das für Relationen? Könntest du vielleicht ein kleines Codebeispiel der Anwendung (nicht Implementierung) einer
Relation
-Klasse zeigen?WTFs/Minute schrieb:
Und: Wäre es besser mit Referenzen statt mit Pointern zu arbeiten?
Ich persönlich nehme normalerweise Referenzen, wenn
NULL
nicht erlaubt ist und sich der Verweis nicht ändert. Aber es gibt diesbezüglich recht unterschiedliche Ansichten.
-
Nexus schrieb:
Was sind denn das für Relationen? Könntest du vielleicht ein kleines Codebeispiel der Anwendung (nicht Implementierung) einer
Relation
-Klasse zeigen?Schwer zu sagen, ein vernünftiges Beispiel habe ich nicht. Die Anwendung setzt bzw. löst die Relation zwischen den abgeleiteten Klassen und ich schaue, wie es sich mit Funktionsaufrufen wie solchen verhält:
// unter der Annahme, zwischen d1 und d2 bestünde die Relation r d1->get_r()->get_d2()->set_foo1(); d2->get_r()->get_d1()->get_foo2(); // kein toller Code, ich weiß
Die Relation könnte sein: Vertrag zwischen Käufer und Verkäufer. Oder Heirat zwischen zwei Personen bzw. irgendeine verwandtschaftliche Beziehung zueinander.
// Beispiele kauefer->get_vertrag()->get_verkaeufer()->get_telefon(); mann->get_heirat()->get_frau()->get_schwester(); frau->get_heirat()->get_kinder();
Bisher wüsste ich keinen Weg das "eleganter" zu lösen. Außer d1/kaeufer/mann und d2/verkaeufer/frau direkt miteinander zu verbinden über entsprechende Attribute. Eine Ebene zum Verwalten der Relationen finde ich aber - aus dem Bauch raus - sinnvoller.
Achja: Im Forum ist mir nun schon ein paarmal der Hinweis auf boost aufgefallen. Scheint ja etwas zu sein, was ich verwenden sollte, sofern ich ernsthaft programmieren möchte?
-
Beziehungen zwischen Entities (Objejte mit Identität) lassen sich auf viele verschiedene Arten modellieren. Als erstes stellt sich immer die Frage, wie die Identität bestimmt ist. Liegen deine Personen oder Vertagspartner in einer Datenbank werden C++ Zeiger oder Referenzen nicht das richtige Mittel sein und es muss eine zusätzliche ID verwaltet werden.
Die nächste Frage stellt sich nach dem Ort wo die Beziehungen gespeichert werden. Das einfachste ist sicher erstmal in der Klasse selber.class Person { typedef Person* PersonID; // so, typedef unsigend long PersonID; // oder so? vector<PersonID> friends_; };
Ein hier wahrscheinlich flexibleres Design wäre es Beziehungen zwischen Personen in einer extra Klasse zu speichern. Neue Arten von Beziehungen (Verwandschaft, Kollegen..) können hinzugefügt werden, ohne
Person
zu ändern.class Person { public: PersonID Id() const; }; class PersonRepository { public: Person& ByID(PersonID); }; typedef boost::bimap<PersonID, PersonID> friendships;
Das Verwalten und Pflegen der Beziehungen sollte dann noch irgendwo gekapselt sein.
Einen sinnvollen Anwendungsfall für dein Vererbungswahn, den du da in deinem Beispiel betreibst kann ich jetzt nicht konstruieren. Wenn du ein konkretes Beispiel hast kannste ja nochmal fragen.
Achja, und ja boost ist für jeden ernsthaften C++ Entwickler extrem sinnvoll. Wie man im Beispiel sieht, bietet boost (neben sehr vielen weiteren Dingen) einen guten Container für bidirektionale Beziehungen (bimap oder die allgemeine Form mutli_index_container).
-
Achja: Im Forum ist mir nun schon ein paarmal der Hinweis auf boost aufgefallen. Scheint ja etwas zu sein, was ich verwenden sollte, sofern ich ernsthaft programmieren möchte?
Ja, Boost sind sehr brauchbare Bibliotheken, die praktisch alle Kandidaten für den C++-Standard sind.
Zum Beispiel C++0x setzt sich aus einem nicht zu verachtenden Teil aus Boost-Features zusammen.