Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Klassenbeziehungen mit Hilfe von Zeigern/Referenzen optimieren

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 <
000
19.06.2008, 14:23 Uhr
~B.Nutzer
Gast


Hallo,

ich bin ein Newbi in Sachen C++, komme eigentlich aus der Java-Welt. Daher habe ich auch ziemlich grossen Respekt vor Zeigern.

Ich habe in den letzten Monaten ein Backend fuer mein Programm geschrieben. Dieses funktioniert, allerdings habe ich grosse Ineffizienz in kauf genommen, um (wilde) Zeiger zu vermeiden und die Benutzung meiner Klasse einfach zu halten.

Meine aktuelle Vorgehensweise kann ich anhand dieses Beispiels ganz gut beschreiben:
Ich habe drei Klassen. Eine Klasse Blog, eine Klasse Entry und eine weitere Klasse Category. Die Klasse Category enthaelt nur Daten.
Die Klasse Entry besitzt allerdings ein Category.


C++:
class Entry {
public:
    //..
    Category& category() { return m_category; }
    const Category& category() const { return m_category; }
    void setCategory(const Category &c) { m_category = c; }
private:
    Category m_category;
    //...
};


Allerdings soll die Klasse Blog, neben einer Liste vom Typ Entry, auch alle Kategorien in einem Set verwalten. Da ich, wie gesagt, mich nicht traute Zeiger zu verwenden, habe ich einen sehr Ineffizienz weg gewählt:


C++:
class Blog {
public:
    //..
    void addEntry(const Entry &entry) {
        addCategory(entry.category());
        m_entries.push_back(entry);
    }
        //...
private:
    std::set<Category> m_categories;
    std::list<Entry> m_entries;
};


Wie vielleicht die Methode addEntry schon verrät behält jedes Entry seine Kopie von Category. Gleichzeit wird beim hinzufuegen eines neuen Entry aber dessen Kategorie ins Set kopiert. Natuerlich darf ich bei diesem Vorgehen auch keine Referenzen/Zeiger auf Entry herausgeben. Jede Aenderung an einem vorhandenen Entry erzwingt das loeschen des alten und hinzufuegen des neuen Entry (wozu ich eine Methode replaceEntry(...)
besitze).

Da mein echtes Backend erheblich mehr solcher Konstrukte enthaelt (ein "Entry" ist z.B. ein Baum, es gibt wiederkehrende Entry wozu es eine Klasse ScheduledEntry gibt die selbst eine Liste vom Typ Entry unterhaelt, etc. pp.) faellt mir die Verwaltung inzwischen extrem schwer und der Overhead bereitet schlaflose Nächte.

Ich weiss das vieles einfacher waere wenn ein Entry z.B. seinen Blog referenzieren koennte und selbst nur eine Referenz oder Pointer (vielleicht auch einen auto_ptr, weak_ptr, shared_ptr oder der gleichen) auf seine Category besitzen wuerde.

Allerdings weiss ich nicht wirklich wie ich das a) sicher und b) sauber Modellieren/Implementieren kann - vor allem auch unter dem Gesichtspunkt das dieses "backend" leicht richtig zu benutzen ist (und damit schwer falsch). Daher meine Frage an einen, aus meiner Sicht, Profi in Sachen C++: Wie wuerde dieses Beispiel von einem erfahrendem C++ Programmierer implementiert werden?

Vielen, vielen Dank schon einmal!
B.Nutzer
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
19.06.2008, 15:10 Uhr
0xdeadbeef
Gott
(Operator)


Ui, das sind ja gleich drei Wünsche auf einmal.

Alle Probleme lassen sich aus dem obrigen Code nicht wirklich ablesen; allerdings vermute ich, dass, wenn du Zeiger um jeden Preis vermeidest, die Baumstruktur extrem problematisch für dich sein dürfte. Bäume sind ohne Zeiger nämlich ziemlich unmöglich zu implementieren. Außerdem vermute ich von der Nomenklatur der Klassen her, dass du eigentlich gar keine Baumstruktur hast, sondern einen zyklischen Graphen, worin ein Entry locker von mehreren anderen referenziert werden kann. (Dass der Graph potentiell zyklisch ist, bedeutet u.a., dass eine einfache Referenzzählung zur Speicherverwaltung nicht ausreicht)

Von einem erfahrenen Programmierer (unabhängig von der Sprache) würde das wohl in einer relationellen Datenbank implementiert werden. Ich könnte mir eine read-on-demand-Implementierung der Entry-Klasse vorstellen, die die Information nachlädt, wenn sie verlangt wird, und loslässt, wenn sie eine Zeit lang nicht mehr abgefragt wurde. Für ein genaues Layout müsste ich mich allerdings eine Weile mit Bleistift und Papier hinsetzen.
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
19.06.2008, 17:02 Uhr
~B.Nutzer
Gast



Zitat von 0xdeadbeef:
Ui, das sind ja gleich drei Wünsche auf einmal.

Oh... nein - drei Wünsche auf einmal wollte ich gar nicht. ;-)


Zitat von 0xdeadbeef:
Alle Probleme lassen sich aus dem obrigen Code nicht wirklich ablesen; allerdings vermute ich, dass, wenn du Zeiger um jeden Preis vermeidest, die Baumstruktur extrem problematisch für dich sein dürfte. [...]

Ms. Verständnis. Mit dem Code wollte ich gar nicht alle meine Proble aufzeigen... sorry. Und der Begriff Baumstruktur ist von mir wirklich schlecht gewaehlt. Nein - er ist sogar falsch. Zur Zeit habe ich weder eine Baumstruktur noch einen Graphen. Einen solchen auf meinem Weg zu implementieren ist auch, wie du schon sagtest, eigentlich unmoeglich. Bisher habe ich z.B. deshalb auf einen Kategorie-Baum komplett verzichtet.

Der Beispiel-Code sollte nur ein generelles Problem zeigen. Schon bei der Struktur der drei genannten Klassen habe ich einige (hier noch loesbare) Probleme. Fuer mich sind sie hier aber nur deshalb loesbar, weil Category in einem Set gespeichert ist und eine Kategorie selbst keine Unterkategorien besitzen kann.

Ich hoffe das, wenn mir jemand ein "richtige" Implementierung fuer diese drei Klassen zeigt, ich diese auch auf meine "echten" Probleme anwenden kann. Bzw. ich hoffe das ich dann ueberhaupt erst einmal das Design so einfacher Beziehungen in den Griff bekomme.

Im Prinzip weiss ich das Entry einen Zeiger auf Blog besitzen muesste. Dann koennte Entry selbst die Verwaltung des Set's uebernehmen und es muesste keine unnoetigen Kopien von Category mehr geben. Auch koennte ich dann eine (nicht const) Referenz auf Entry zurueckgeben, ohne das meine interne Struktur zerstoert wird. Nur wie Regel ich die Erzeugung einer neuen Entry Instanz - Entry* createEntry() in Blog, einen Konstruktor Entry(Blog *b) oder vielleicht Entry(Blog &b)... ? Wie stelle ich sicher das der Zeiger in Entry auf die Category nicht zerstoert wird...

Mit solchen Problemen faengt es bei mir schon an. Was ich mir also "Wünsche" ist ein Beispiel wie ich die drei gezeigten Klassen besser implementieren kann.

Ich denke das mir ein gutes Beispiel hierfuer wirklich schonmal einen grossen Schritt nach vorne bringt. Zur Zeit habe ich hier einfach einen maechtigen Knoten im Kopf...

Danke!


P.S: Vielleicht weiss auch jemand eine wirklich gute Lektuere (Code, Tutorial, Buch) das einem wie mir zeigt wie man von einem Klassendesign zu einer C++ Implementierung kommt. Einfach etwas das meinen Knoten im Kopf etwas loest. Buecher zum einen und zum anderen Thema besitze ich genug - aber wie ich von einem zum anderem komme (auf dem C++ Weg) weiss ich eben nicht.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
19.06.2008, 19:59 Uhr
0xdeadbeef
Gott
(Operator)


Hm. Du stellst hier eine Designfrage; um die sinnvoll zu beantworten, muss ich wissen, was du eigentlich genau machen willst. Es gibt einige funktionale Möglichkeiten, die ich dir auch erläutern kann, wenn du willst, aber wenn du fragst, was der sauberste Weg ist, muss ich wissen, wohin der Weg führen soll.

Was genau willst du da eigentlich machen?
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra

Dieser Post wurde am 19.06.2008 um 19:59 Uhr von 0xdeadbeef editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
20.06.2008, 15:05 Uhr
~B.Nutzer
Gast


Hi,

erst einmal muss ich mich bei dir bedanken. Danke, das du Augenscheinlich wirklich bemueht bist mir zu helfen.

Ein Problem, das ich in meinem Programm habe, ist das oben gezeigte. Meine Klassen heissen anders und verwenden Qt statt die STL, aber das spielt nicht wirklich eine Rolle.


C++:
class Category {
public:
    bool isValid() { return !m_name.empty(); }
    //getter und setter

private:
    std::string m_name;
    std::string m_description;
};



So, nur mit ein paar mehr Nutzdaten und Methoden, sieht meine Klasse Category aus. Entry ist wie oben gezeigt, was fehlt sind lediglich Nutzdaten.

Mein Problem besteht nun darin das es eine Category nicht doppelt geben darf, gleichwohl aber natuerlich mehrere Entry auf die gleiche Kategorie "verweisen" duerfen und es auch Kategorien geben kann, auf die noch nicht (oder nicht mehr) verwiessen wird. Ganz wie in einem echten Blog kann der Benutzer also vorher Kategorien anlegen und spaeter Eintraege diesen Kategorien zuweisen.

Damit es Category nicht doppelt gibt, speicher ich diese in meiner zentralen Klasse Blog in einem Set. Da Entry aber keine Referenz/keinen Zeiger auf Blog besitzt, muss Blog sich selbst darum kuemmern alle Categorien zu sammeln bzw. alle Kopien zu loeschen, falls das gefordert wird.

Da ich natuerlich nicht wirklich eine Category loeschen und dessen Zeiger auf 0 setzen kann, gilt eine Category als geloescht, bzw. gilt das ein Entry keine Category besitzt, wenn Category::isValid false zurueckgibt.


C++:
class Blog {
public:
    //..
    void addEntry(const Entry &entry) {
        addCategory(entry.category());
        m_entries.push_back(entry);
    }

    void addCategory(const Category &c) {
        if(c.isValid())
            m_category.insert(c)
    }

    void replaceCategory(const Category &from, const Category &to) {
        addCategory(to);
        for(int i=0; i < m_entries.size(); ++i) {
            if(m_entries[i].category() == from)
                m_entries[i].setCategory(to);
        }
    }

    void removeCategory(const Category &c) {
        replaceCategory(c, Category());
        m_categories.remove(c); // Falls das in der stl nicht geht, wuerde ich ein entsprechendes stl Konstrukt verwenden.
    }

    //...
private:
    std::set<Category> m_categories;
    std::list<Entry> m_entries;
};


Will ich also ein Category loeschen, durchlaufe ich alle Entry in der Liste und ersetze ggf. derren Category durch ein leeres und damit invalides Category. Abgesehen davon das dieses "Design" total ineffizent und Ressourcen verschwenderisch ist, kann ich so auch keine (nicht const) Referenzen auf Entry herausgeben und verlange damit vom Programmierer eine replaceEntry() Methode zu verwenden, moechte er eine Aenderung an ein Entry vornehmen.

Um das Problem zu loessen muesste, so denke ich, Entry im Konstruktor eine Reference (oder doch einen Zeiger?) auf Blog uebernehmen.


C++:
class Entry {
public:
    Entry(Blog &blog) : m_blog(blog) {}
    //...
private:
    Blog &m_blog;
}


Das sieht fuer mich ganz gut aus.
Dann wuerde ich in Entry keine Kopie sondern einen Zeiger auf Category halten. Aber spaetestens da komme ich nicht weiter. Den so muesste ich immer noch alle Entry durchlaufen wenn ich eine Kategorie loeschen moechte. Also vielleicht lieber einen weak_ptr... oder...?

Ich hoffe es ist klarer geworden, wo meine Probleme in diesem Code liegen. Ich weiss das es schwer ist einem Newbie zu verstehen (da er sich nicht klar ausdruecken kann) und moechte mich daher nochmals fuer deine Muehe und deine Geduld bedanken.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
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
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
006
20.06.2008, 22:40 Uhr
~B.Nutzer
Gast


Sehr cool. Ich muss es wirklich nochmal sagen: Danke!

Ich habe den Code zwar bisher nur gelesen und nicht versucht umzusetzen, aber auf den ersten Blick verstehe ich ihn soweit. Selbst drauf gekommen waere ich aber sicher nicht.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 <     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: