Liste von Basisklassenzeigern: suche elegante Lösung für den Zugriff auf runtergecastete Elemente [gelöst]
-
Hallo zusammen,
eine Klasse verwaltet eine Liste von B-Zeigern und soll direkten Zugriff auf die D-Zeiger bieten. Bisher habe ich das so gelöst:
#include <vector> #include <typeinfo> class B { public: virtual ~B() {}; }; class D1 : public B {}; class D2 : public B {}; class Cont { public: ~Cont(); void Add( B* ptr ); template <typename T> T* GetFirst(); private: std::vector <B*> _elems; }; Cont::~Cont() { for ( std::vector <B*>::iterator it( _elems.begin() ); it != _elems.end(); ++it ) { delete *it; *it = 0; } } void Cont::Add( B* ptr ) { _elems.push_back( ptr ); } template <typename T> T* Cont::GetFirst() { for ( std::vector <B*>::iterator it( _elems.begin() ); it != _elems.end(); ++it ) if ( typeid( **it ).name() == typeid( T ).name() ) return dynamic_cast <T*> ( *it ); return 0; } int main() { Cont c; c.Add( new D1() ); c.Add( new D2() ); D1* d1Ptr( c.GetFirst <D1> () ); D2* d2Ptr( c.GetFirst <D2> () ); return 0; }Hier im Minimalbeispiel habe ich die shared_ptr (deshalb der Cont-dtor), die typedefs, das const-Gerödel und dass B pure virtual ist mal weggelassen.
Es funktioniert so zwar, und das Ganze ist in meiner Anwendung auch nicht performance-kritisch, allerdings werde ich das Gefühl nicht los, dass man das eleganter lösen kann.
Hat jemand einen Vorschlag, wie das dann aussehen könnte?
Gruß
Dobi
-
Dobi schrieb:
eine Klasse verwaltet eine Liste von B-Zeigern und soll direkten Zugriff auf die D-Zeiger bieten.
Die Frage ist: Warum? Kannst du nicht virtuelle Funktionen einrichten, welche für jede abgeleitete Klasse anders implementiert sind? Dann könntest du im Benutzercode die Abstraktion bewahren und müsstest nicht explizit Typen unterscheiden, was in der Tat nicht sehr elegant ist.
Übrigens solltest du nie
std::type_info::name()für irgendetwas ausser für Debuggingzwecke benutzen, also auch keine Vergleiche wieif ( typeid( **it ).name() == typeid( T ).name() )anstellen. Der Standard garantiert nämlich nichts über den Namen, prinzipiell dürfen verschiedene Typen den gleichen Namen haben.
*it = 0;Ist ausserdem unnötig, weil das Objekt nicht länger existiert.
-
Stimmt, das Auf-0-Setzen ist unnötig. In so einem übersichtlichen Fall macht es nichts aus, ansonsten verwahre ich aber nur ungern ungültige Zeiger. In der eigentlichen Anwendung kümmern sich ja eh die Zeiger selbst um die Zerstörung ihrer Objekte.
Oh, dann ist std::type_info::name() hier ja wirklich doof. Ich seh gerade, dass type_info eh schon nen Vergleichsoperator hat. Dann also so:
if ( typeid( **it ) == typeid( T ) )Wie ich nur mit virtuellen Funktionen klar kommen könnte, weiß ich leider nicht. Der Container ist bei mir ein Objekt, dass aus einem Bild raussegmentiert wurde, und trägt Eigenschaften (B) wie Kontur (D1), Histogramm (D2) usw.
Könnte ich sonst noch irgendwas besser machen? Gehts vielleicht ohne dynamic_cast?
-
Dobi schrieb:
if ( typeid( **it ) == typeid( T ) )Typischerweise benutzt man für solche Tests dynamic_cast selbst (wobei zu beachten ist, dass dieser Test weniger streng ist)
template <typename T> T* Cont::GetFirst() { for ( std::vector <B*>::iterator it( _elems.begin() ); it != _elems.end(); ++it ) if ( T* p = dynamic_cast <T*> ( *it ) ) return p; return 0; }Ohne mehr über die Anwendung zu wissen, ist es schwierig, Designempfehlungen zu geben.
Ein denkbare Möglichkeit wäre, nicht eine Liste von B-Zeigern, sondern mehrere Listen entsprechender D-Zeiger zu verwalten, ungefähr so:
class B { public: ~B() {}; }; class D1 : public B {}; class D2 : public B {}; class cont_helper_base { virtual ~helper_base() {} }; template <typename T> class cont_helper cont_helper_base { std::vector <T*> elems; public: ~cont_helper() { for ( std::vector<T*>::iterator it = elems.end(); it != elems.begin(); ) delete *--it; } T* GetFirst() { return elems.empty() ? 0 : elemns.front(); } }; class Cont { public: template <typename T> void Add( T* ptr ) { lists[ &typeid( T ) ].push_back( ptr ); } template <typename T> T* GetFirst() { map_type::iterator it = lists.find( &typeid( T ) ); return it == lists.end() ? 0 : static_cast<cont_helper<T>*>( it->second )->GetFirst(); } ~Cont() { for ( map_type::iterator it = lists.begin(); it != lists.end(); ++it ) delete it->second; } private: typedef std::map<const std::type_info*, cont_helper_base*> map_type; map_type lists; };Wie angedeutet ist es damit nicht einmal notwendig, dass B virtuell ist, überhaupt werden keinerlei Beziehungen zwischen den verschiedenen Typen benötigt.
P.S.: Ich gehe davon aus, dass für jedes T genau ein type_info-Objekt existiert, und somit ein Vergleich der Adressen ausreicht. Könnte allerdings sein, dass das nicht vorgeschrieben ist - dann müsste man noch einen geeigneten Comparator für die map benutzen.
-
Ah, type_info als key in einer Map find ich ne sehr coole Idee. Wie du die For-Schleifen geschrieben hast und die bedingten Ausdrücke benutzt, gefällt mir auch. Ich versuche jetzt aber erstmal, deinen Code irgendwie kompilierbar zu bekommen.

Edit: Da ich eigentlich nur ein Objekt pro Typ in Cont benötige, habe ich es mal etwas vereinfacht:
#include <map> #include <typeinfo> class TypeInfo { public: TypeInfo( const std::type_info& ti ) : _ptr( &ti ) {} ~TypeInfo() {} const std::type_info* _ptr; }; bool operator==( const TypeInfo& lhs, const TypeInfo& rhs ) { return ( lhs._ptr == rhs._ptr ); } bool operator<( const TypeInfo& lhs, const TypeInfo& rhs ) { return ( lhs._ptr->before( *rhs._ptr ) ); } class B { public: virtual ~B() {}; }; class D1 : public B {}; class D2 : public B {}; class Cont { public: ~Cont(); template <typename T> void Add( T* ptr ); template <typename T> T* Get(); private: typedef std::map <const TypeInfo, B*> TMap; TMap _elems; }; Cont::~Cont() { for ( TMap::iterator it( _elems.begin() ); it != _elems.end(); ++it ) { delete it->second; it->second = 0; } } template <typename T> void Cont::Add( T* ptr ) { _elems[ TypeInfo( typeid( T ) ) ] = ptr; } template <typename T> T* Cont::Get() { TMap::iterator it( _elems.find( TypeInfo( typeid( T ) ) ) ); return it == _elems.end() ? 0 : dynamic_cast <T*>( it->second ); } int main() { Cont c; c.Add( new D1() ); c.Add( new D2() ); D1* d1Ptr( c.Get <D1> () ); D2* d2Ptr( c.Get <D2> () ); return 0; }Deine Grundidee ist ja aber noch drin. Find ich gut. So werde ich es wohl machen. Danke.

Modern C++ design (Seite 38) sagt übrigens, dass der Standard nicht garantiert, dass die Referenz, die typeid zurückgibt, immer auf das gleiche type_info-Objekt zeigt. Hab deshalb nen kleinen Wrapper drumgelegt.
-
Der Header heißt übrigens type_info.
-
Bei mir (gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)) nicht.
-
Tatsächlich.