Polymorphie(, ... oder doch nicht?)
-
Hallo,
ich habe ein kleines Designproblem.
Ich habe verschiedene Elemente, die alle eine Basis-Klasse haben. Alle Klassen sind Template-Klassen. Jedes Element hat einen eindeutigen Namen.template< typename T > class BasicElement { std::string elementName; //nicht änderbar. }; template< typename T > class ElementA : public BasicElement<T> { //... }; template< typename T > class ElementB : public BasicElement<T> { //... }; template< typename T > class ElementC : public BasicElement<T> { //... };Nun habe ich Manager für andere Klassen, die mit den Elementen arbeiten. Diese Manager sollen folgende Funktionen besitzen:
Elemente hinzufügen
Elemente wiedergeben
Anzahl Elemente ausgeben
Elemente löschenAusgeschrieben in etwa so:
template< typename T > class Manager { //Elemente hinzufügen bool addElementA( ElementA<T> *newElem ); bool addElementB( ElementA<T> *newElem ); bool addElementC( ElementA<T> *newElem ); //Elemente über den Namen finden und wiedergeben ElementA<T>* getElementA( std::string name ); ElementB<T>* getElementB( std::string name ); ElementC<T>* getElementC( std::string name ); //Elemente über den Namen löschen bool deleteElement( std::string name ); };Nun kann es aber auch durchaus vorkommen, dass irgendwann mal ein Element hinzukommt( z.b. "ElementD" ). Es wäre dann ein erheblicher Aufwand, für das neue Element alle Methoden neu zu schreiben.
Zudem brauche ich auch eine Möglichkeit, auf alle Elemente zuzugreifen( Für alle Elemente die gleiche Operation, also mit "BasicElement" arbeiten )Mein Ansatz wäre, eine Liste zu machen. In etwa so:
template< typename T, typename ElementTyp > class ElementList { std::vector<ElementTyp*> elements; size_t numberOfElements; std::vector<BasicElement<T>*> addToBasicVector( ); }; template< typename T > class Manager { ElementList<T, ElementA<T>> allA; ElementList<T, ElementB<T>> allB; ElementList<T, ElementC<T>> allC; //Ein Vektor mit den Basisklassen aller Elemente erstellen, da man so "allgemeine Methoden" anwenden kann. std::vector<BasicElement<T>*> getAllElements( ) { std::vector<BasicElement<T>*> result; this->allA.addToBasicVector( result ); this->allB.addToBasicVector( result ); this->allC.addToBasicVector( result ); return result; } };Aber dann hörts schon wieder auf. Wie krieg ich sowas ordentlich hin? Da will mir irgendwie nix gescheites einfallen

Für den User des ganzen(ein Framework), soll der Zugriff so möglich sein:
struct DataObject { ... } Manager<DataObject> man; man.createElementA( "bla", /* Parameter zur Erzeugung von Element A */ ); man.createElementB( "blubb", /* Parameter zur Erzeugung von Element A */ ); //do something... ElementA<DataObject>* tmp = man.getElementA( "bla" ); //irgendetwas mit tmp tun... //Mit allen Elementen arbeiten std::vector<BasicElement<DataObject>*> elems; size_t i_max = man.getAllElements( elems ); for( size_t i = 0; i < i_max; ++i ) { beHappy( elems[i] ); } man.deleteElement( "blubb" ); man.deleteElement( "bla" );Der Manager muss in der Lage sein, Elemente nach ihrem Typ wiedergeben( Ich will also für "bla" nicht den Zeiger auf die Basis-Klasse, sondern ein Zeiger auf ElementA ). Aber dennoch soll er per Basisklassenzeiger "Allgemeine Methoden" für _alle_ Elemente anwenden.
Ich hoffe, ich hab mich irgendwie verständlich ausgedrückt, sodass man mir helfen kann

MfG. Christoph Ludwig
-
ChristophLu schrieb:
Der Manager muss in der Lage sein, Elemente nach ihrem Typ wiedergeben( Ich will also für "bla" nicht den Zeiger auf die Basis-Klasse, sondern ein Zeiger auf ElementA ).
Kannst du das den Anwender aufbürden? So dass er vllt schreiben muß
ElementA<DataObject>* s = m.getElement<ElementA<DataObject>* >("erde");
-
Das wäre eine Lösung für das "Frontend".
Aber so richtig gefallen will dir mir auch nicht. Eine sehr unschöne Variante wäre ja, per Makro die Funktionen und Daten erstellen zu lassen.
Sowas in der Art:#define F(e) \ std::vector<e*> data ## e; \ size_t numberOf ## e ## s; \ get ## e( std::string name ) { \ for( size_t i = 0; i < this->numberOf ## e ## s; ++i ) { \ if( this->data ## e[i] == NULL ) continue; \ if( this->data ## e[i]->getName() == name ) \ return this->data ## e[i]; \ } \ }So könnte man die Benötigten Funktionen erstellen lassen. Anwendung:
F( ElementA<DataObjectType> ) F( ElementB<DataObjectType> ) F( ElementC<DataObjectType> )Nur die Variante finde ich sehr, sehr unschön, weshalb ich sie nur ungern einsetzen würde.
Eine Überlegung zum Backend:
Sollte ich alle Elemente pertemplate< typename DataObjectType > class Manager { std::vector<BasicElement<DataObjectType>*> elements; }speichern, oder sollte ich jedes Element einzeln speichern? à la
template< typename DataObjectType > class Manager { std::vector<ElementA<DataObjectType>*> elemAs; std::vector<ElementB<DataObjectType>*> elemBs; std::vector<ElementC<DataObjectType>*> elemCs; }Bei der ersten Variante müsste ich bei den Rückgabefunktionen mit
dynamic_castarbeiten, richtig?Ach, nochwas: Wie speichere ich die Zeiger am besten? ich muss damit rechnen, dass der User viele Zugriffe darauf macht. Ist Vector da eher "langsam" und sollte man etwas anderes verwenden oder ist der Vektor da ok?
(Deque soll ja schneller sein, aber da gibts ja grad nen Thread dazu. Den werd ich einfach mal verfolgen.)MfG. Ich
-
ChristophLu schrieb:
ich muss damit rechnen, dass der User viele Zugriffe darauf macht. Ist Vector da eher "langsam" und sollte man etwas anderes verwenden oder ist der Vektor da ok?
Das kommt doch ganz drauf an! In den meisten Fällen ist std::vector die optimale Lösung. Wenn du aber viele Einfügungen/Löschungen in der Mitte hast solltest du dich nach einem anderen Container umschauen. Da ist vector langsam, da intern die Elemente umkopiert werden müssen. Ist aber mit Zeigern auch nicht SO wild, sind ja schnell kopiert.
-
ChristophLu schrieb:
Bei der ersten Variante müsste ich bei den Rückgabefunktionen mit
dynamic_castarbeiten, richtig?So hatte ich mir das gedacht, ein Container
std::vector<BasicElement<DataObjectType>*>- ich würde nicht diverse Container verwenden um deine Probleme ganz aus den Weg zu gehen. Allerding muß dann der App-Coder den genauen Typ des Objektes kennen das er zurückhaben will.
Zu der Perfomanz würde ich mir eher überlegen ob das Suchen der Objekte via String nicht verbessert werden kann. Möglicherweise wäre ein assoziativer Container sinnvoll. Das musst du aber selber entscheiden was du häufiger brauchst - Suchen vs wahlfreier Zugriff vs Einfügen/Löschen.
-
Ich habs jetz so gemacht, das alle meine Elemente ihren Typ kennen. Typ und Name kann man nicht verändern. Über den Namen kann der User seine Elemente später identifizieren. Ist wie in JavaScript mit document.getElementById( "..." )

Anstelle von String könnte ich auch Zahlen oder anderes nehmen, klar. Aber Strings fand ich am besten.enum ELEMENT_TYPE { ET_A = 0, ET_B, ET_C }; template< typename DataType > class BasicElement { private: ELEMENT_TYPE type; std::string name; public: BasicElement( std::string newName ) : type( ET_A ) name( newName ) { } ELEMENT_TYPE getType( ) { return this->type; } std::string getName( ) { return this->name; } };Dadurch, dass ich den Typ im Element speichere, kann der User nicht nach einem B-Element mit den Namen "foo" suchen, wenn im Vector das Element "foo" vom Typ C ist. Somit wende ich auch keinen falschen dynamic_cast an, á la:
BasicElement* b = new ElementA( ... ); ElementC *c = dynamic_cast<ElementC>( b );Also gestaltet sich der Manager in etwa so:
template< typename DataType > class Manager { protected: typedef BasicElement<DataType>* basicType; std::vector< basicType > elements; size_t numberOfElements; template< typename ElemType > bool addElement( ElemType *newElem ) { for( size_t i = 0; i < this->numberOfElements; ++i ) { if( this->elements[i] == NULL ) continue; if( this->elements[i]->getName() == newElem->getName() ) return false; //Ein Element mit diesem Namen kommt schon vor. } this->elemens.push_back( newElem ); ++this->numberOfElements; } template< typename ElemType > ElemType* getElement( ELEMENT_TYP type, std::string name ) { for( size_t i = 0; i < this->numberOfElements; ++i ) { if( this->elements[i] == NULL ) continue; if( this->elements[i]->getType() == type && this->elements[i]->getName == name return dynamic_cast<ElemType*>( this->elements[i] ); } return NULL; } public: Manager( ) : numberOfElements( 0 ) { } size_t size( ) { return this->numberOfElements; } size_t deleteElement( std::string name ) { for( size_t i = 0; i < this->numberOfElements; ++i ) { if( this->elements[i] == NULL ) continue; if( this->elements[i]->getName == name ) { this->elements.erase( this->elements.begin() + i ); --this->numberOfElements; return true; } } return false; } bool addElementA( /* A-spezifische Parameter */ ) { ElementA *newElem = new ElementA( ... ); return this->addElement<ElementA>( newElem ); } ElementA* getElementA( std::string name ) { return this->getElement( ET_A, name ); } };So hab ich eigentlich das erreicht, was ich wollte.
Was sagt ihr dazu? verwendbar oder eher grausam?Zu der Perfomanz würde ich mir eher überlegen ob das Suchen der Objekte via String nicht verbessert werden kann. Möglicherweise wäre ein assoziativer Container sinnvoll. Das musst du aber selber entscheiden was du häufiger brauchst - Suchen vs wahlfreier Zugriff vs Einfügen/Löschen.
Das Programm verläuft eigentlich so, dass der User seine Elemente anfangs anlegt. später will man diese eigentlich nur manipulieren(auslesen,ändern,whatever).
- Also Anfangs schnelles Einfügen
- In der Mitte schneller Random Access Zugriff
- Und am Ende des Programmes schnelle Zerstörung
MfG. Ich