Member-Objekte mit Zugriff auf das 'Oberobjekt'.
-
Hallo!
So etwas wie hier würde ich gerne machen.Problemstellung
struct Base { virtual void foo_Nested()=0; }; struct C { void foo_C(int) {..} struct Nested_0 : Base { void foo_Nested() {foo_C(0);} } nested_0; struct Nested_1 : Base { void foo_Nested() {foo_C(1);} } nested_1; struct { // anonymous - but still a subclass of Base?? void foo_Nested() {foo_C(2);} } anonymous; } c; int main() { C c; Base* b = c.nested_0; b->foo_Nested(); }Eigentlich "kennt" die Variable 'c.nested_0' ja ihre eigene Adresse und sollte rein technisch daher auch auf die Adresse von c. Demnach sollten Member-Funktionen von 'C::Nested_0' eben auch auf die Methoden von 'C' zugreifen können. Jedenfalls dann, wenn 'C::nested_0' die einzige Stelle ist, an der eine Instanz von 'C::Nested_0' existiert.
Natürlich gibt es gute Gründe, warum der oben angezeigte Code nicht funktionieren darf. Trotzdem wäre es in diesem Fall nützlich.
Eine andere Methode wäre, in 'Nested' eine Referenz auf C zu speichern. Ich würde aber gern diese Redundanz verhindern, da ein Pointer/Referenz ja zusätzlich Speicher kostet!!
Eine ziemlch rabiate Lösung sieht so aus (Barton-Nackmann-Trick?).
struct Base { virtual void foo_Nested()=0; }; struct NestedInC_0 : Base { void foo_Nested() { ((C*)this)->foo_C(0); // 'compile time polymorphism' } }; struct NestedInC_1 : Base { void foo_Nested() { ((C*)this)->foo_C(1); } }; struct C : NestedInC_0, NestedInC_1 { void foo_C(int) {} }; int main() { C c; Base* b = (NestedInC_0*)(&c); b->foo_Nested(); }Mit der Mehrfachvererbung erreiche ich, daß die beiden Member nachher eine unterschiedliche Speicheradresse haben. Allerdings bin ich mir nicht sicher ob mir das auch wirklich tut was es soll.. Wahrscheinlich gibt es sogar eine ambiguity oder sowas!
Gibt es da nicht etwas eleganteres??
Und fragt mich bitte nicht wieder wozu das gut sein soll

-
Erstens: Deine Klassen Nested_0 etc leben zwar innerhalb von C, sind aber völlig eigenständig - ergo haben sie von sich aus auch keinen Zugriff auf ein C-Objekt (es sei denn, du gibst ihnen eins mit).
Zweitens: Wenn diese Klassen das Interface von B übernehmen sollen, mußt du auch von B erben.
Drittens: Ja, mit dieser Mehrfachvererbung könntest du Probleme mit Mehrdeutigkeiten bekommen (obwohl - durch den expliziten Cast wird das wieder aufgelöst).
Viertens: Wozu soll das gut sein? *scnr*
-
CStoll schrieb:
Erstens: Deine Klassen Nested_0 etc leben zwar innerhalb von C, sind aber völlig eigenständig - ergo haben sie von sich aus auch keinen Zugriff auf ein C-Objekt (es sei denn, du gibst ihnen eins mit).
Das ist richtig. Jedoch ist für ein Objekt c vom Typ C das member c.nested immer an derselben Speicherstelle relativ zum Anfang von c. Umgekehrt kann also von der Adresse von c.nested auf die Adresse von c geschlossen werden. Das Konzept der Member-Klasse ist hier nicht genau das was ich haben will - denn die Klasse C::Nested ist ja nicht auf das Objekt c.nested beschränkt. Letztlich suche ich nach einem anderen Konzept.
Zweitens: Wenn diese Klassen das Interface von B übernehmen sollen, mußt du auch von B erben.
Ups, ja toll das muss ich wohl mal ändern.
Drittens: Ja, mit dieser Mehrfachvererbung könntest du Probleme mit Mehrdeutigkeiten bekommen (obwohl - durch den expliziten Cast wird das wieder aufgelöst).
Also ich habe das einmal ausprobiert und es funktioniert! Es wird genau die virtuelle Funktion aufgerufen die ich haben will. Allerdings finde ich die Methode irgendwie 'unseriös'.
Viertens: Wozu soll das gut sein? *scnr*
Ok wenn du willst

Ich werde aber wieder nur ein 'Ersatzproblem' beschreiben, tut mir leid!!
Also, stell dir vor du hast eine state machine mit 20 states und noch ein paar Variablen, die dieser Menge noch zusätzliche Dimensionen verleihen (also haben wir am Ende viel mehr Zustände). Jeder der 20 states macht etwas völlig anderes mit den Variablen, und dem Eingabesymbol, und wird dabei ws in einen anderen von den 20 Zuständen wechseln.
Deswegen mache ich eine Basisklasse 'class State' mit virtuellen Funktionen process_symbol_A(Symbol symbol), die an alle 20 State-Klassen vererbt wird. Die Implementierung dieser Funktion in den State_0, State_1 usw Klassen muss Zugriff auf die Variablen in der state machine haben. Da es aber für jede state machine genau 20 dieser Zustände gibt, also genau ein Objekt jeder Zustandsklasse pro state machine, mache ich diese zu membern von class StateMachine. Zusätzlich habe ich in StateMachine noch einen Pointer des Typs State* auf eine der 20 states, den aktiven natürlich. Wenn die state machine ein Symbol (Befehl) erhält, wird von diesem Pointer aus die virtuelle Funktion process_symbol(Symbol symbol) aufgerufen.
Rein technisch ist dies machbar, weil ich ja von der Adresse der states auf die Adresse der state machine schließen kann, und zwar zur compile-Zeit. Nach C++ Regeln muss ich aber irgendwo eine Referenz auf die state machine entweder beim Funktionsaufruf übergeben oder in jedem state speichern. Aus verschiedenen Gründen möchte ich dies vermeiden (indirection level, speicher).
Mein Problem zuhause ist noch etwas komplizierter. Hier hat die state machine unterschiedliche 'Cluster', die zur Laufzeit hinzugefügt oder entfernt werden können. Ein Cluster eines Typs hat eine feste Anzahl von States. Jeder dieser states muss nur Zugriff auf die Variablen des Clusters haben. In der state machine ist nun ein Pointer des Typs State* auf irgendeinen state in irgendeinem Cluster gespeichert. Die virtuelle Funktion, kombiniert mit einem Symbol, gibt mir dann die Adresse des Folgestates in einem anderen oder demselben Cluster. Die states müssen also die Adresse ihres Clusters kennen. Die traditionelle Möglichkeit wäre, dies in einer Referenz zu speichern - gefällt mir aber nicht!!
So in etwa, das war jetzt immer noch vereinfacht.
-
lemonhead schrieb:
CStoll schrieb:
Erstens: Deine Klassen Nested_0 etc leben zwar innerhalb von C, sind aber völlig eigenständig - ergo haben sie von sich aus auch keinen Zugriff auf ein C-Objekt (es sei denn, du gibst ihnen eins mit).
Das ist richtig. Jedoch ist für ein Objekt c vom Typ C das member c.nested immer an derselben Speicherstelle relativ zum Anfang von c. Umgekehrt kann also von der Adresse von c.nested auf die Adresse von c geschlossen werden. Das Konzept der Member-Klasse ist hier nicht genau das was ich haben will - denn die Klasse C::Nested ist ja nicht auf das Objekt c.nested beschränkt. Letztlich suche ich nach einem anderen Konzept.
Ist zwar low-level, aber eventuell könntest du mit offsetof() zurückkommen auf dein Mutter-Objekt (wobei allerdings niemand garantieren kann, daß das wirklich existiert)
Viertens: Wozu soll das gut sein? *scnr*
Ok wenn du willst

Ich werde aber wieder nur ein 'Ersatzproblem' beschreiben, tut mir leid!!
Also, stell dir vor du hast eine state machine mit 20 states und noch ein paar Variablen, die dieser Menge noch zusätzliche Dimensionen verleihen (also haben wir am Ende viel mehr Zustände). Jeder der 20 states macht etwas völlig anderes mit den Variablen, und dem Eingabesymbol, und wird dabei ws in einen anderen von den 20 Zuständen wechseln.
Deswegen mache ich eine Basisklasse 'class State' mit virtuellen Funktionen process_symbol_A(Symbol symbol), die an alle 20 State-Klassen vererbt wird. Die Implementierung dieser Funktion in den State_0, State_1 usw Klassen muss Zugriff auf die Variablen in der state machine haben. Da es aber für jede state machine genau 20 dieser Zustände gibt, also genau ein Objekt jeder Zustandsklasse pro state machine, mache ich diese zu membern von class StateMachine. Zusätzlich habe ich in StateMachine noch einen Pointer des Typs State* auf eine der 20 states, den aktiven natürlich. Wenn die state machine ein Symbol (Befehl) erhält, wird von diesem Pointer aus die virtuelle Funktion process_symbol(Symbol symbol) aufgerufen.
Rein technisch ist dies machbar, weil ich ja von der Adresse der states auf die Adresse der state machine schließen kann, und zwar zur compile-Zeit. Nach C++ Regeln muss ich aber irgendwo eine Referenz auf die state machine entweder beim Funktionsaufruf übergeben oder in jedem state speichern. Aus verschiedenen Gründen möchte ich dies vermeiden (indirection level, speicher).
Klar könntest du das machen, aber das dürfte nicht besonders portabel sein. Als Alternative würde mir noch einfallen, EINE globale state machine zu verwenden, deren Adresse dann bekannt ist.
Aber imho dürfte ein Pointer nicht SOO viel Platz wegnehmen (oder arbeitest du schon an den Grenzen deines Speichers?)
Mein Problem zuhause ist noch etwas komplizierter. Hier hat die state machine unterschiedliche 'Cluster', die zur Laufzeit hinzugefügt oder entfernt werden können. Ein Cluster eines Typs hat eine feste Anzahl von States. Jeder dieser states muss nur Zugriff auf die Variablen des Clusters haben. In der state machine ist nun ein Pointer des Typs State* auf irgendeinen state in irgendeinem Cluster gespeichert. Die virtuelle Funktion, kombiniert mit einem Symbol, gibt mir dann die Adresse des Folgestates in einem anderen oder demselben Cluster. Die states müssen also die Adresse ihres Clusters kennen. Die traditionelle Möglichkeit wäre, dies in einer Referenz zu speichern - gefällt mir aber nicht!!
Also entweder die State's müssen sich den Cluster merken oder die State Machine notiert sich, in welchem Cluster sie gerade unterwegs ist, was anderes fällt mir da nicht ein (wenn du die Cluster zur Laufzeit anpassen willst, entfällt der Weg über eine globale Variable).
(btw, was machen die einzelnen States, wenn ihr Folgestatus mit seinem Cluster gelöscht wurde?)
-
CStoll schrieb:
Ist zwar low-level, aber eventuell könntest du mit offsetof() zurückkommen auf dein Mutter-Objekt (wobei allerdings niemand garantieren kann, daß das wirklich existiert)
Werde ich mir mal anschauen! Scheint mir aber genauso rabiat wie die Mehrfachvererbung!
Klar könntest du das machen, aber das dürfte nicht besonders portabel sein. Als Alternative würde mir noch einfallen, EINE globale state machine zu verwenden, deren Adresse dann bekannt ist.
Aber imho dürfte ein Pointer nicht SOO viel Platz wegnehmen (oder arbeitest du schon an den Grenzen deines Speichers?)
Der Speicherverbrauch wäre vertretbar, aber durch das level of indirection ('Indirektionslevel'?) würde es ein bißchen langsamer werden? Schließlich muss man erstmal die Adresse des Clusters auswerten.
Also entweder die State's müssen sich den Cluster merken
siehe oben
oder die State Machine notiert sich, in welchem Cluster sie gerade unterwegs ist
Naja das wollte ich ja eigentlich auch vermeiden. Dann muss man ja bei jedem state Übergang beides neu setzen.
(btw, was machen die einzelnen States, wenn ihr Folgestatus mit seinem Cluster gelöscht wurde?)
Das ist kein Problem. Ich lösche nur dann ein Cluster, wenn es in der Struktur nicht mehr verbunden ist.
Also, am besten präzisiere ich nochmal was ich eigentlich vorhabe. Die state machine beschreibt es noch nicht ganz richtig.
Stelle dir eine Horde Spinnen vor, die innerhalb des state machine Graphen unterwegs ist. Die Spinnen kriegen die Befehle des Alphabets (rechts, links, usw) und gehen von einem Knoten zum anderen.
Diese Viecher funktionieren so ähnlich wie Iteratoren, sie dienen dazu eine Datenstruktur auszulesen. Nur bewegen sie sich nicht nur vorwärts und rückwärts, sondern entlang der Zustandsübergänge / Graphkanten. Die 'Spinnen' (sorry mir fällt jetzt kein besseres Wort ein) muss man zu jeder Zeit schnell erzeugen können, und sie müssen sich effektiv fortbewegen können. Deswegen die Performance-Argumente. Bei einem STL-container erwartest du ja auch, daß ein iterator sich effektiv verhält. Da kommt es auf jeden Minibefehl an.
Die 'Spinne' soll die Adresse des Zustands, und einen integer-Index speichern. Der Index zeigt dann auf ein Objekt innerhalb des Clusters.
Das Ding wird schon etwas ausgebremst wegen der runtime Polymorphie (virtual). Ohne die müsste man aber jede Menge if/else oder switch benutzen. Ob das am Ende schneller wäre??
-
lemonhead schrieb:
Die 'Spinne' soll die Adresse des Zustands, und einen integer-Index speichern. Der Index zeigt dann auf ein Objekt innerhalb des Clusters.
Ich weiß nicht, was jetzt schneller ist - die Cluster-Adresse direkt notieren oder die Cluster-Adresse aus der eigenen Adresse und dem Index berechnen.
(PS: Sorry, wenn ich zu wenig Erfahrung mit Mikro-Optimierung habe - aber für mich hört sich dein Problem etwas paranoid an
Man kann es auch übertreiben mit der Optimierung (vorher solltest du mal darüber nachdenken, ob diese Indirektion wirklich so zeitkritisch gegenüber den eigentlichen Berechnungen ist, die die "Spinnen" durchführen))
-
paranoid, kann schon sein

Ob es wirklich einen Vorteil bringt? Ich habe auch nur eine begrenzte Vorstellung davon ..class C { int x; int y; int z; } c;Ich gehe mal davon aus, jedes Mal wenn ich auf eine Variable in einer Klasse zugreifen will, muss ich ja eh eine Adressen-Addition durchführen.
- Ich habe die Adresse des Objekts c, und will auf y zugreifen - Addition um die Speichergröße eines Integers.
- Ich habe die Adresse von z, und will auf y zugreifen - Addition minus der Speichergröße eines Integers. Oder Adresse -2 zum Objekt c, und dann +1 bis y. Der Compiler macht daraus -1.
-
Na fast - in dem Fall wirst du wohl immer von c ausgehen, um dessen Member zu erreichen.
Und das Problem bei deiner Adress-Bastelei ist ganz einfach:
Die Klasse Nested_1 geht davon aus, daß sie immer als zweites Element im Inneren eines C-Objekts existiert. Solange das gegeben ist, klappt deine Rechnerei recht gut. Allerdings dürftest du dich spätestens dann in die Nesseln setzen, wenn du Mested_1 im Alleingang verwenden willst.
Und im allgemeinen Fall mit der State Machine und ihren Clustern kommt noch dazu, daß die State's nicht zur Compilezeit wissen, wo sie stehen - da ist die Adressberechnung eher der größere Aufwand://das willst du vermeiden: Cluster* m_cluster; ... m_cluster->var1 = 4711; //so sieht deine Lösung aus (wenn ich deine Bemerkungen richtig deute): int m_index; ... reinterpret_cast<Cluster*>(this-m_index)->var1 = 4711;
-
Ich sehe jetzt noch nicht ganz was du meinst.. vielleicht hilft das hier?
struct StatePilot { virtual StatePilot* processSymbol(int& index_in_cluster, int symbol)=0; }; struct StateHandle { StatePilot* state_pilot; int index_in_cluster; }; struct Spider : StateHandle { void processSymbol(int symbol) { state_pilot = state_pilot->processSymbol(index_in_cluster, symbol); }; }; struct ClusterType_A { struct ClusterObject { StateHandle linked_from_A0; StateHandle linked_from_A1; }; vector<ClusterObject> cluster_objects; struct StatePilot_A0 : StatePilot { virtual State* processSymbol(int& index_in_cluster, int symbol) { if(symbol==1) { ++index_in_cluster; return this; } else if(symbol==-1) { --index_in_cluster; return this; } else { StatePilot* res = cluster_objects[index_in_cluster].linked_from_A0.state_pilot; index_in_cluster = cluster_objects[index_in_cluster].linked_from_A0.index_in_cluster; return res; } } } state_pilot_a0; struct StatePilot_A1 : StatePilot { ... } state_pilot_a1; }; struct ClusterType_B { ... };
-
Ein sehr schönes Sprachkonstrukt wären übrigens anonyme member, die trotzdem eine Basisklasse haben können! Anonyme Klasse KÖNNEN nämlich garnicht irgendwo anders instanziiert werden! Zum Beispiel so.
struct Base {...}; struct C { // version eins, in c++ nicht möglich Base {...} nested; // version zwei, in c++ nicht möglich Base nested {...}; // version drei, in c++ nicht möglich struct {...} nested : Base; // version drei, in c++ nicht möglich struct : Base {...} nested; // in C++ möglich, vererbt aber nichts struct {...} nested; };btw, gibt es nicht sowas wie ein C++ wish-list Forum für neue Versionen?? Ich glaube, ich hätte da einiges beizutragen!!