005
20.06.2008, 21:31 Uhr
0xdeadbeef
Gott (Operator)
|
Hmm. Okay, ich glaube, langsam verstehe ich, worauf du hinauswillst.
Okay...was die beste Lösung ist, ist natürlich streitbar, und hängt davon ab, wie flexibel das System sein muss. Ich gehe mal von einer einfachen Situation aus, in der ein Eintrag immer nur in einem Blog auftaucht, aber mehrere Kategorien haben kann. In dem Fall macht es Sinn, die Kategorien als Kategorien des Blogs zu betrachten, und den Einträgen Verweise darauf zu geben - also Zeiger. Ich gehe hier mal von einem einfachen Grundansatz aus und fülle die Details später ein.
Prinzipiell willst du also etwas in der Art:
| C++: |
class blog;
class category { blog *blog_; };
class entry { std::vector<category*> categories_; };
class blog { std::vector<entry> entries_; std::set<category> categories_; };
|
(Ich benutze hier die GNU-Notation, darin fühle ich mich zu Hause. foo_ bedeutet eine Membervariable namens foo)
Da wir hier eine einseitige Verweislage haben, reicht eine Referenzzählung aus. Das heißt, wir setzen
| C++: |
class category { public: inline std::size_t entry_count() const { return refcount_; } inline void increase_refcount() { ++refcount_; } inline void decrease_refcount() { --refcount_;
// Wenn wir nicht mehr gebraucht werden, zur Entfernung vormerken if(refcount_ == 0) { blog_->schedule_for_deletion(this); } }
private: blog *blog_; std::size_t refcount_; };
|
Mit den meisten, wenn nicht allen, gängigen Compilern dürfte die Klasse sich hier auch selbst löschen können, allerdings ist das streng genommen nicht korrekt, weil wir uns danach in einer Methode eines nicht-existenten Objekts wiederfänden. Generell ist es schlechter Stil, deswegen lasse ich die Klasse sich zur Löschung vormerken, und blog_ später, wenn wir aus category raus sind, die Drecksarbeit übernehmen. In blog müssen entsprechende Funktionalitäten eingebaut werden; das könnte zum Beispiel so aussehen, dass blog eine std::queue<category*> von Kategorien enthält, die nach der Zerstörung eines entries gelöscht werden. (Threadsicherheit für den Moment außen vorgestellt)
Dann lassen jedes mal, wenn die Kategorie zugewiesen wird, den Entry-Count hochgehen, und wenn sie entfernt wird, runter. Also z.B.
| C++: |
void entry::add_category(category *cat) { categories_.push_back(cat); cat->increase_entry_count(); }
entry::~entry() { for(std::vector<category*>::iterator i = categories_.begin(); i != categories_.end(); ++i) { i->decrease_entry_count(); } }
|
...in vorher angelegten Methoden der Klasse entry.
Das wäre der grundlegende Mechanismus; allerdings ist diese enge Verwebung von Einträgen, Kategorien und Blogs nicht wirklich wünschenswert. Warum soll sich ein Blogeintrag mit der Speicherverwaltung des Blogs rumschlagen müssen? Macht nicht viel Sinn. Was also tun?
Ich würde hier einen äußerst nützlichen kleinen Trick anwenden, nämlich eine Proxyklasse, die den betreffenden Code aus der entry-Klasse auslagert. Das sähe dann in etwa so aus:
| C++: |
class blog;
class category { public: category(blog *myblog) : blog_(myblog) {}
std::size_t refcount() const { return refcount_; } // ...yadda yadda, wie oben
private: blog *blog_; std::size_t refcount_; };
class category_adapter { public: category_adapter(category &cat) : cat_(cat) { cat_.increase_refcount(); }
~category_adapter() { cat.decrease_refcount(); }
// ...hier Zugriffsmethoden für entry auf cat
private: category &cat_; };
class entry { public: void add_category(category_adapter const &cat) { categories_.push_back(cat); }
private: std::vector<category_adapter> categories_; };
|
Wenn entry zerstört wird, wird categories_ zerstört, und dann auch die category_adapter-Objekte darin; deren Destruktoren erniedrigen den Referenzzähler im betroffenen category-Objekt, und das merkt sich ggf. zur Zerstörung vor. Merke, wie entry keinerlei Speicherlogik enthält.
Je nach Geschmackslage kannst du auch category_adapter category und category category_backend nennen oder so, es läuft auf das selbe hinaus. Oh, und ich habe hier alle Methoden direkt in die Klassen geschrieben; normalerweise würde man das natürlich auf Header- und Quellcodedateien auftrennen.
Das wäre so meine grundsätzliche Idee. Ergibt das soweit Sinn? -- Einfachheit ist Voraussetzung für Zuverlässigkeit. -- Edsger Wybe Dijkstra |