Implementierung eines statischen Visitors
-
Ein möglicher Ansatz zur Automatisierung:
// ---------------------------------------------------------------- // Bibliothek (virtuelle Destruktoren weggelassen) struct BaseVisitor { ... }; template <class D> struct Visitor { Visitor(); // Registriert Visit() dieser Klasse virtual void Visit(D&) = 0; // muss überschrieben werden }; // ---------------------------------------------------------------- // Anwendung struct MyVisitor : BaseVisitor, Visitor<Derived1>, Visitor<Derived2> { virtual void Visit(Derived1&); virtual void Visit(Derived2&); };Der Rest bliebe ungefähr gleich.
Fändet ihr das benutzerfreundlicher? Ich sehe eigentlich nicht viele Vorteile, abgesehen davon, dass man
DynamicDispatchernicht direkt anfassen muss.- Nur Methoden einer einzelnen Klasse zu verwenden ist auch mit
DynamicDispatchermöglich. - Man dupliziert auch hier Informationen: Abgeleitete Typen kommen als Template-Argumente von
Visitorsowie als Funktionsparameter vonVisit()vor. - Der Ansatz hier hat recht viel Overhead, da neben den VTables wahrscheinlich auch ein
dynamic_castpro Visitor nötig ist. Zusätzlich zum Dispatch-Mechanismus, der ja auch nicht ganz gratis ist. - Man ist grundsätzlich weniger flexibel, weil bereits bestehende Funktionen nicht zum Besuch verwendet werden können. Man muss eine Überladung in
MyVisitorschreiben. Hingegen kannDynamicDispatchmit freien Funktionen, Memberfunktionen und Funktoren umgehen.
Aber seid ehrlich, was würdet ihr als Benutzer lieber verwenden?
- Nur Methoden einer einzelnen Klasse zu verwenden ist auch mit
-
Nexus schrieb:
Mein Dynamic-Dispatcher ist inzwischen lauffähig, ich muss hauptsächlich noch ein paar Feineinstellungen vornehmen. Wenn ich das richtig sehe, habe ich sogar was Ähnliches wie dein Code nachgebaut, nur halt mehr auf die Laufzeit konzentriert.
Jepp, ich denke dass das unter der Haube recht ähnlich zu meinem Ansatz ist. Kannst Du den Code von Deinem Dispatcher posten, insbesondere von Call und Register?
Nexus schrieb:
Ist nun halt kein Funktor mit überladenen
operator()-Funktionen mehr, aber das scheint eh nicht zu gehen. Zumindest nicht ohne Template-Gehacke und Ableiten von etlichen Klassen. Oder kann man mit Boost.Fusion das Ganze so hinkriegen, dass ein Visitor des Benutzers so aussieht (von mir aus kann er von einer Basisklasse erben)?Ich glaub prinzipiell kannst Du in
fusion::mapjeden beliebigen Typ reinpacken. Funktoren hab ich nur gewählt weil die ziemlich allgemein sind.
-
Nexus schrieb:
Ein möglicher Ansatz zur Automatisierung:
// ---------------------------------------------------------------- // Bibliothek (virtuelle Destruktoren weggelassen) struct BaseVisitor { ... }; template <class D> struct Visitor { Visitor(); // Registriert Visit() dieser Klasse virtual void Visit(D&) = 0; // muss überschrieben werden }; // ---------------------------------------------------------------- // Anwendung struct MyVisitor : BaseVisitor, Visitor<Derived1>, Visitor<Derived2> { virtual void Visit(Derived1&); virtual void Visit(Derived2&); };Der Rest bliebe ungefähr gleich.
Fändet ihr das benutzerfreundlicher?
Vielleicht müssen sich ja beiden Methoden gar nicht ausschliessen. Wenn Du das hinbekommst kann der User entscheiden was er lieber benutzt, entweder einen grossen Visitor oder viele kleine. Ich persönlich finde den ersten Ansatz ein bisschen besser. Die Mehrfachvererbung ist mir ein kleines bisschen suspekt.
-
headmyshoulder schrieb:
Kannst Du den Code von Deinem Dispatcher posten, insbesondere von Call und Register.
Was? Der ist höchst geheim!

Nein, kann ich gerne machen. Aber wie schon angetönt muss das Ganze noch verbessert werden, richtig getestet habe ich auch noch nicht. Ich würds also noch nicht direkt verwenden. Wenn du aber interessiert bist, kann ich dir eine E-Mail schreiben, wenn ich fertig bin (das wird wahrscheinlich Teil einer grösseren Bibliothek).
Hier mal das Klassentemplate
DynamicDispatchersoweit (Memberfunktionen der Übersicht halber inline). Jetzt fällt mir gerade auf, dass der Code in denRegister()-Überladungen in eine private Funktion ausgelagert werden könnte. Undstd::mapwerde ich wohl durch einen sortiertenstd::vectorersetzen. Es gibt noch einiges zu tun... Möglicherweise werde ich später auch einen Dispatcher für Funktionen mit zwei Parametern implementieren.
// B = Basisklasse der Hierarchie // R = Rückgabetyp der dispatchten Funktionen template <class B, typename R = void> class DynamicDispatcher : private NonCopyable { private: // Map, die type-ids auf Funktionen abbildet (TypeInfo ist ein Wrapper um std::type_info) typedef std::map<detail::TypeInfo, detail::FunctionBase<B, R>*> FnMap; public: // Destruktor ~DynamicDispatcher() { FOREACH(typename FnMap, myMap, itr) // Makro für STL-For-Schleife delete itr->second; } // Registrierung von globalen Funktionen // D = Abgeleitete konkrete Klasse template <class D> void Register(R (*fn)(D&)) { typename FnMap::const_iterator itr = myMap.find(typeid(D)); if (itr != myMap.end()) throw InvalidFunctionRegistration(); myMap[typeid(D)] = new detail::GlobalFunction<B, D, R>(fn); } // Registrierung von Memberfunktionen mit this-Zeiger // D = Abgeleitete konkrete Klasse // C = Klasse mit aufzurufender Memberfunktion template <class D, class C> void Register(R (C::*fn)(D&), C* thisPtr) { typename FnMap::const_iterator itr = myMap.find(typeid(D)); if (itr != myMap.end()) throw InvalidFunctionRegistration(); myMap[typeid(D)] = new detail::MemberFunction<B, D, R, C>(fn, thisPtr); } // Registrierung beliebiger Funktionsobjekte // D = Abgeleitete konkrete Klasse // Fn = Typ des Funktors template <class D, typename Fn> void Register(const Fn& fn) { typename FnMap::const_iterator itr = myMap.find(typeid(D)); if (itr != myMap.end()) throw InvalidFunctionRegistration(); myMap[typeid(D)] = new detail::GlobalFunction<B, D, R, Fn>(fn); } // Aufruf R Call(B& arg) const { typename FnMap::const_iterator itr = myMap.find(typeid(arg)); if (itr == myMap.end()) throw InvalidFunctionCall(); return itr->second->Call(arg); } private: FnMap myMap; };Und hier noch die Implementierung innerhalb des Namensraums
detail. Ich hätte stattdessenstd::tr1::functionnehmen können, aber wegen Problemen bei Derived-Base-Konvertierungen der Argumente habe ich das selbst gebaut (ist ja nicht viel Code):template <class B, typename R> struct FunctionBase { virtual R Call(B& ref) = 0; virtual ~FunctionBase() {} }; template <class B, class D, typename R, typename Fn = R(*)(D&)> struct GlobalFunction : FunctionBase<B, R> { explicit GlobalFunction(const Fn& fn) : fn(fn) { } virtual R Call(B& ref) { return fn( static_cast<D&>(ref) ); } Fn fn; }; template <class B, class D, typename R, class C> struct MemberFunction : FunctionBase<B, R> { typedef R (C::*MemFn)(D&); MemberFunction(MemFn fn, C* thisPtr) : fn(fn) , thisPtr(thisPtr) { } virtual R Call(B& ref) { return (thisPtr->*fn)( static_cast<D&>(ref) ); } MemFn fn; C* thisPtr; };Vielen Dank nochmals für deine Hilfe! Ich hoffe, du bist nicht enttäuscht, dass ich nun doch nicht Boost.Fusion verwende. Aber ich werds mir für später merken...

-
headmyshoulder schrieb:
Vielleicht müssen sich ja beiden Methoden gar nicht ausschliessen. Wenn Du das hinbekommst kann der User entscheiden was er lieber benutzt, entweder einen grossen Visitor oder viele kleine.
Hm... Ich halte die Schnittstelle generell lieber klein. Hier ist es vielleicht besser, wenn es nur einen Weg gibt.
headmyshoulder schrieb:
Die Mehrfachvererbung ist mir ein kleines bisschen suspekt.
Ja, das Prinzip habe ich wie so vieles von Alexandrescu. Ist ein bisschen speziell, das stimmt... Aber wären dir Typlisten lieber?

-
Nexus schrieb:
Nein, kann ich gerne machen. Aber wie schon angetönt muss das Ganze noch verbessert werden, richtig getestet habe ich auch noch nicht. Ich würds also noch nicht direkt verwenden. Wenn du aber interessiert bist, kann ich dir eine E-Mail schreiben, wenn ich fertig bin (das wird wahrscheinlich Teil einer grösseren Bibliothek).
Ja, gerne.
Dein Ansatz ist ein bisschen anders, sollte aber die gleiche Funktionalität haben. Ich glaube die Call-Funktion muss auch noch templatisiert werden, oder? Was interessant ist, dass bei beiden Ansätzen ein Typ erstmal auf den Parent-Type runtergecastet wird und danach wieder hoch. Das runtercasten passiert bei beiden Ansätzen an unterschiedlichen Stellen, bei Dir nach der Suche für den spezifischen Visitor und bei mir davor.
Nexus schrieb:
headmyshoulder schrieb:
Vielleicht müssen sich ja beiden Methoden gar nicht ausschliessen. Wenn Du das hinbekommst kann der User entscheiden was er lieber benutzt, entweder einen grossen Visitor oder viele kleine.
Hm... Ich halte die Schnittstelle generell lieber klein. Hier ist es vielleicht besser, wenn es nur einen Weg gibt.
Das was ich meinte kannst Du schon machen.0 Zum Beispiel einen grossen Visitor benutzen, und dessen Methoden registrieren; oder viele kleine Vistor benutzen, welche separat regisitriert werden.
-
headmyshoulder schrieb:
Ich glaube die Call-Funktion muss auch noch templatisiert werden, oder?
Ich denke nicht, denn beim Aufruf ist ja nur eine Basisklassenreferenz (also
B&) vorhanden. Der dynamische Typ kann nicht durch ein Template erkannt werden.headmyshoulder schrieb:
Das was ich meinte kannst Du schon machen.0 Zum Beispiel einen grossen Visitor benutzen, und dessen Methoden registrieren; oder viele kleine Vistor benutzen, welche separat regisitriert werden.
Das wäre dann eher auf Benutzerseite, oder? Das habe ich auch mit "Nur Methoden einer einzelnen Klasse zu verwenden ist auch mit DynamicDispatcher möglich" gemeint. Also sowas:
struct MyVisitor { void operator() (Derived1&); void operator() (Derived2&); }; int main() { MyVisitor v; DynamicDispatcher dispatcher; dispatcher.Register<Derived1>(&MyVisitor::operator(), &v); dispatcher.Register<Derived2>(&MyVisitor::operator(), &v); ... }Ein Problem gibt es allerdings. Da ich
typeidverwende, muss der Typ exakt stimmen. Derived-to-Base-Konvertierungen sind beim Aufruf also nicht möglich. Ich weiss nicht, ob es dafür überhaupt eine akzeptable Lösung gibt. Alexandrescu hat das Problem in seinem Buch leider nicht gelöst...struct Derived2a : Derived2 {}; int main() { // dispatcher wie oben ... Derived2a obj; dispatcher.Call(obj); // Ruft nicht die Derived2-Version auf } // da typeid(Derived2a) != typeid(Derived2)
-
Wenn du einen hübschen Funktor mit einem oder mehreren ()-Operatoren haben willst, muss m.E. dieser irgendwie zugänglich machen, für welche Typen er konzipiert ist (also den ()-Operator überladen hat). Anders ist eine statische Wahl der richtigen Funktion zur Compilezeit (aus den überladenen Funktionen) ja nicht möglich.
Meine Idee sähe so aus:
template<class Derived, class R, class SupportedTypes> // SupportedTypes ist MPL-Sequenz aller unterstützten Typen des Visitors struct visitor_base { BOOST_MPL_ASSERT((boost::mpl::is_sequence<SupportedTypes>)); typedef R result_type; template<class T> result_type apply(T* ptr) { return apply_impl<SupportedTypes>(static_cast<Derived&>(*this), ptr); } private: template<class Types, class T> result_type apply_impl(Derived& d, T* ptr, boost::mpl::false_type = boost::mpl::false_type()) { typedef boost::mpl::front<Types>::type current_type; typedef boost::mpl::pop_front<Types>::type next; if(typeid(*ptr) == typeid(current_type)) // Juhuu, Visitor unterstützt Typ return d(boost::polymorphic_downcast<current_type&>(*ptr)); // Also ()-Operator aufrufen else return apply_impl<next>(d, ptr, boost::mpl::empty<next>); } template<class Types, class T> result_type apply_impl(Derived& d, T* ptr, boost::mpl::true_type) { // der Visitor unterstützt den Typ von *ptr nicht. Wahlweise assert, ignore o.ä. return result_type(); } };Genutzt werden kann das dann so:
struct my_visitor : visitor_base<my_visitor, int, boost::mpl::vector<Derived1, Derived2, Derived2a> > { int operator()(Derived1&) const; int operator()(Derived2&) const; }; struct container { std::vector<base*> objects; template<class Visitor> void apply_visitor(const Visitor& v) { FOREACH(base* b, objects) v.apply(b); // vergessen wir den Rückgabewert... } }; // ... container c; c.apply_visitor(my_visitor());(alles ungetestet. Solltest du kein boost verwenden können, kannst du das wenige mpl-Zeugs sicher auch selbst schreiben).
Das hat immernoch das Problem mit dem Derived2a vs. Derived, man muss Derived2a in der Liste der unterstützden Typen angeben. Der Derived&-()-Operator wird dann trotzdem aufgerufen. Das sollte aber fast nicht anders gehen, es sei denn man könnte zur Laufzeit ermitteln, ob ein Objekt einen dynamnischen Typ mit einem speziellen Basistyp hat.
Und man muss die unterstützen Typen mehrfach angeben, wie schon eingangs erläutert (ich denke es ist nicht möglich, die Typen mit Metaprogrammierung aus den in der Klasse überladenen ()-Operatoren zu ermitteln - was ist, wenn man ein Template nutzt?)Eventuell ist dein Ansatz über Dynamic Dispatch aber auch günstiger.
-
Nexus schrieb:
headmyshoulder schrieb:
Ich glaube die Call-Funktion muss auch noch templatisiert werden, oder?
Ich denke nicht, denn beim Aufruf ist ja nur eine Basisklassenreferenz (also
B&) vorhanden. Der dynamische Typ kann nicht durch ein Template erkannt werden.Aber dann ist die typeid in
R Call(B& arg) const { typename FnMap::const_iterator itr = myMap.find(typeid(arg)); ... }immer gleich, nämlich die typeid von B. Oder übersehe ich was?
Nexus schrieb:
Ein Problem gibt es allerdings. Da ich
typeidverwende, muss der Typ exakt stimmen. Derived-to-Base-Konvertierungen sind beim Aufruf also nicht möglich.Hier könnte vielleicht sowas wie
boost::is_base_of<>helfen aus den TypeTraits helfen, ich bin mir aber nicht sicher.
-
ipsec schrieb:
Wenn du einen hübschen Funktor mit einem oder mehreren ()-Operatoren haben willst, muss m.E. dieser irgendwie zugänglich machen, für welche Typen er konzipiert ist (also den ()-Operator überladen hat). Anders ist eine statische Wahl der richtigen Funktion zur Compilezeit (aus den überladenen Funktionen) ja nicht möglich.
Meine Idee sähe so aus:
Deine Idee find ich auch ganz nett, allerdings hast Du immer das Problem, dass Du
apply()immer mit dem "richtigen" Typ aufrufen musst. Inipsec schrieb:
struct container
{
std::vector<base*> objects;template<class Visitor>
void apply_visitor(const Visitor& v)
{
FOREACH(base* b, objects)
v.apply(b); // vergessen wir den Rückgabewert...
}
};ist
bbeispielsweise immer vom Typbase*, obwohl man hier eigentlich den "richtigen" Type haben will. Das passiert in meinem Beispiel nicht, da dort probiert wird, den Typ runterzucasten, und wenn das klappt wird der Visitor mit dem runtergecastet Typ besucht.
-
ipsec schrieb:
Meine Idee sähe so aus [...]
Danke auch für deinen Vorschlag. Allerdings brauche ich da wieder massiv Metaprogrammierung und das sogar im Interface (Typlisten als Template-Argumente). Das möchte ich dem Benutzer lieber ersparen. Aber Alternativ-Vorschläge sind immer gerne gesehen, vielleicht brauch ich die später mal

headmyshoulder schrieb:
Aber dann ist die typeid in
R Call(B& arg) const { typename FnMap::const_iterator itr = myMap.find(typeid(arg)); ... }immer gleich, nämlich die typeid von B. Oder übersehe ich was?
Ja,
typeidermittelt zur Laufzeit den dynamischen Typen (sofernBpolymorph ist, was Voraussetzung ist). Das wäre hier also die abgeleitete Klasse, die tatsächlich vonargreferenziert wird.headmyshoulder schrieb:
Hier könnte vielleicht sowas wie
boost::is_base_of<>helfen aus den TypeTraits helfen, ich bin mir aber nicht sicher.Daran habe ich auch schon gedacht, nur haben wir wieder das ursprüngliche Problem des Threads. Man kann die beiden statischen Typinformationen (Typen
Derived2undDerived2a) nie vereinen, weil statische Typinformationen über virtuelle Funktionen hinweg nicht erhalten bleiben. Der einzige Weg wäre, beide Typen zusammen in einem Funktionstemplate zu verwenden. Aber dass der Benutzer die Vererbungsbeziehung dafür explizit nochmals kennzeichnen müsste, ist etwas schade. Manchmal wären minimale Reflection-Features halt schon praktisch...
-
Nexus schrieb:
Ja,
typeidermittelt zur Laufzeit den dynamischen Typen (sofernBpolymorph ist, was Voraussetzung ist). Das wäre hier also die abgeleitete Klasse, die tatsächlich vonargreferenziert wird.Sorry, dass ich nochmal nerve. Ich hab hier folgendes Miniprogramm:
#include <iostream> #include <typeinfo> using namespace std; class virtual_base { ~virtual_base( void ) { } }; class derived_one : public virtual_base { }; class static_base { }; class derived_two : public static_base { }; void test_one( virtual_base &b ) { cout << ( typeid( virtual_base ) == typeid( b ) ) << endl; cout << ( typeid( derived_one ) == typeid( b ) ) << endl; } void test_two( static_base &b ) { cout << ( typeid( static_base ) == typeid( b ) ) << endl; cout << ( typeid( derived_two ) == typeid( b ) ) << endl; } int main( int argc , char **argv ) { virtual_base *b = new derived_one(); test_one( *b ); static_base d2; derived_two d; test_two( d ); test_two( d2 ); }In den beiden Funktionen wird immer die erste Bedingung auf
trueevaluiert, und genau das sollte bei Dir nicht passieren. Oder steh ich gerade total auf dem Schlauch?
-
Nur weil du deine Basisklasse
virtual_basenennst, ist sie noch nicht polymorph. Dazu muss sie mindestens eine virtuelle Funktion haben. Und ihr Destruktor ist nicht virtuell
-
Nexus schrieb:
ipsec schrieb:
Meine Idee sähe so aus [...]
Danke auch für deinen Vorschlag. Allerdings brauche ich da wieder massiv Metaprogrammierung und das sogar im Interface (Typlisten als Template-Argumente). Das möchte ich dem Benutzer lieber ersparen.
Ok. Also keine Metaprogrammierung auf Nutzerseite.
Das Problem ist eigentlich nur, dass die überladene Funktion statisch zur Compilezeit ermittelt werden muss und mir fällt da keine Möglichkeit ein, außer eine definierte Liste abgeleiteter Typen durchzuprobieren.Aber gut, diese Liste muss ja nicht der Nutzer stellen. Wie wäre es denn mit sowas (Namespaces hab ich jetzt mal weggelassen):
// Klassen der Bibliothek class Base {}; class Derived1 : Base {}; class Derived2 : Base {}; class Derived2a : Derived2 {}; // Typliste aller abgeleiteten Klassen von Base, in der Bibliothek, muss dem Nutzer nicht bekann sein template<class BaseType> struct derived_types; // Deine Spezialisierung template<> struct derived_types<Base> { typedef vector<Derived1, Derived2a, Derived2, Base> types; // Wichtig: Derived2a vor Derived2, Base ganz hinten }; // Visitorbasisklasse (diesmal mal ohne Rückgabewert, kann man aber einfach hinzufügen) // Grundidee ist die Gleiche wie zuvor template<class Visitor> struct visitor_base { template<class Base> void apply(Base* b) const { typedef derived_types<Base> types_to_check; apply_impl<types_to_check>(dynamic_cast<Visitor&>(*this), b); } // Fallback, wird vom Visitor geerbt void operator()(...) const {} private: template<class Types, class Base> static void apply_impl(Visitor& v, Base* b, false_ = false_()) const { typedef front<Types>::type current; typedef pop_front<Types>::type next; current* ptr = dynamic_cast<current*>(b); // dynamic_cast, also Derived2a-vs-Derived2-Problem gelöst if(ptr) v(*ptr); // Fallback, wenn Visitor keinen eigenen Operator anbietet else apply_impl<next>(v, b, empty<next>()); } template<class Types, class Base> static void apply_impl(Visitor&, Base*, true_) const { // b entspricht keinem Typ in der Liste, sollte nie erreicht werden (v.a. wegen dem Base in der Liste) } };Der Nutzer kann jetzt ganz lieblich z.B. folgendes machen:
struct my_visitor : visitor_base<my_visitor> { void operator()(Derived1&) const {} void operator()(Derived2a&) const {} }; // ... Base *b = new ...; my_visitor v; v.apply(b);Von der ganzen Metaprogrammierung ist also auf Nutzerseite nichts mehr zu sehen.
Problem ist jetzt vor allem, dass du
derived_types<Base>pflegen musst. Ein echtes Problem wird das dann, wenn der Nutzer auch selbst Typen ableiten kann, die dann mit berücksichtigt werden sollen.
Dann muss der Nutzer irgendwie eine Typliste seiner Typen pflegen (ob ähnlich wie beiderived_typesoder als Templateargument) und invisitor_base::applymusst dutypes_to_checkdann aus beiden zusammensetzen. Bei der Gelegenheit kannst du die entstehende Liste dann mit Metaprogrammierung gleich noch so sortieren, dass abgeleitete Klassen immer vor ihren Basisklassen stehen (sonst wird die falsche Überladung aufgerufen, da die Basisklasse zuerst überprüft wird). Das ist dann zwar wieder viel Metaprogrammierung, aber transparent für den Nutzer.Sollte dieser wirklich von jeglicher Form der Metaprogrammierung abgehalten werden und z.B. eine Userspezialisierung von beispielsweise
user_derived_types<Base>nicht in Betracht kommen, kann man das ja auch hinter einem Makro verstecken (z.B.REGISTER_VISITABLE_TYPES). Noch hübscher wird das mit einem MakroREGISTER_VISITABLE_TYPE, mit dem man jeden Typ einzeln registrieren kann (kann man z.B. direkt unter die Klasse schreiben), da weiß ich aber nicht, wie man das umsetzt (also eine Typliste an mehreren Stellen zu erweitern).
Persönlich finde ich aber auch einREGISTER_VISITABLE_TYPES(Base, (UserDerived1)(UserDerived2));auf jeden Fall schöner als irendwas mit dispatcher und Register. Letzteres finde ich auch für den Nutzer komplizierter. Das ist aber sicher auch sehr subjektiv und kommt wohl auch auf deinen konkreten Anwendungsfall an.Ach ja, mein Code hat noch UD durch die Ellipse, wenn du das weg haben willst, musst du dem Fallback-Operator einen anderen Rückgabewert geben und dann z.B. mit
sizeofvor dem Aufruf prüfen, ob denn der Fallback aufgerufen werden würde.
-
Nexus schrieb:
Nur weil du deine Basisklasse
virtual_basenennst, ist sie noch nicht polymorph. Dazu muss sie mindestens eine virtuelle Funktion haben. Und ihr Destruktor ist nicht virtuell
Ok, dass macht Sinn. Danke. Wieder was gelernt.
-
Naja, mit Metaprogrammierung sind halt viele C++-Programmierer nicht sehr vertraut. Die Anwendung von Typlisten geht zwar vielleicht gerade noch, aber wie gesagt kann ich eh kein Boost.MPL verwenden. Und zum C++98-Nachbau von Variadic Templates habe ich nicht wirklich Lust. Einen statischen Visitor bringts wahrscheinlich vor allem, wenn auch die Typen als statische Informationen vorliegen (wie bei
boost::variant). Dann muss man auch nicht von etlichen Klassen erben...ipsec, dein Ansatz erinnert übrigens stark an den Dispatcher von Alexandrescu, der den Grossteil zur Kompilierzeit berechnet. Auch da wird alles mit
dynamic_castdurchprobiert (und dasDerived2-Derived2a-Problem somit schön gelöst). Aber ich möchte mich hier mehr auf die Laufzeit konzentrieren.ipsec schrieb:
Persönlich finde ich aber auch ein
REGISTER_VISITABLE_TYPES(Base, (UserDerived1)(UserDerived2));auf jeden Fall schöner als irendwas mit dispatcher und Register. Letzteres finde ich auch für den Nutzer komplizierter. Das ist aber sicher auch sehr subjektiv und kommt wohl auch auf deinen konkreten Anwendungsfall an.Ich sehe wie schon angedeutet keinen grossen Vorteil darin, sich fest an einen funktor-ähnlichen Visitor zu binden. Die Implementierung wird wahnsinnig viel komplexer, und die Anwendung wird weniger flexibel und sogar langsamer (siehe meine Punkte am Ende der Seite 2).
Klar ist so ein Dispatcher vielleicht ein wenig ein Umdenken, aber den Visitor kriegt man ohnehin nicht schön hin. Man muss entweder von allen Typ-Instanziierungen ableiten oder separate Traits bereitstellen und ist mit Metaprogrammierung wegen der vielen
dynamic_casts besonders bei vielen Klassen um einiges langsamer als mit einemtypeid. Der Dispatch-Mechanismus hat O(n), um alle Klassen durchzuprobieren, und die Rechenzeit für einendynamic_castkann je nach Implementierung auch mit der Anzahl der Klassen steigen.Auch wenn man das
Derived2-Problem löst, kommt dieser Fall nicht genug häufig vor, als dass ich den Mechanismus dafür stark verlangsamen würde. Aber man könnte dem Benutzer die Möglichkeit geben, das selbst auszuwählen. Aber vorerst ist es wohl das Einfachste, man registriert eine entsprechende Funktion einfach mehrmals (für jede assoziierte Klasse).