"Interface-Reflection"



  • Gegrüßet seid ihr!
    Ich habe mich die vergangenen Stunden damit beschäftigt, so einer Art "Interface-Reflection" für meine Klassen hinzufrickeln. Sinn, Unsinn und Zweck sind hier glaube ich gerade erstmal nicht so wichtig. Ich fange mal mit einer zeitlichen Geschichte an.

    Alles begann damit, dass ich zur Laufzeit die unterstützten Schnittstellen meiner Objekte in Erfahrung bringen wollte:

    class Interface {
    public:
        virtual Composite* GetComposite() = 0;
        virtual std::wstring GetName() = 0;
    };
    
    class InterfaceContainer {
    public:
        virtual std::set< std::wstring > GetInterfaceNames() = 0;
        virtual Interface* GetInterface( const std::wstring& ) = 0;
    };
    

    Das war schonmal eine ganz schön tolle Sache, allerdings würde es doch bestimmt aufwendig werden, für all meine konkreten Objecte die entsprechenden Methoden zu überschreiben. Also dachte ich mir, versuche ich mich mal an Helferklassen, die mir das automatisieren:

    class InterfaceContainerDynamic : public InterfaceContainer
    {
    public:
        std::set< std::wstring > GetInterfaceNames() override;
        Interface* GetInterface( const std::wstring& ) override;
    
    protected:
        void RegisterInterface( Interface* i );
        void UnregisterInterface( Interface* i );
    
    private:
        std::map< std::wstring, Interface* > interfaces_;
    };
    
    template< typename Int >
    class InterfaceDynamic : public Int, public virtual InterfaceContainerDynamic
    {
    public:
        InterfaceDynamic()
        {
            RegisterInterface( static_cast< Int* >( this ) );
        }
    
        ~InterfaceDynamic()
        {
            UnregisterInterface( static_cast< Int* >( this ) );
        }
    };
    

    Hey, das funktionierte doch schonmal toll, man litt sein Objekt von einem oder mehreren in InterfaceDynamic verschachtelten Schnittstellen ab und bekam den Rest der Funktionalität für lau.

    Aber halt! Jetzt werden diese Listen ja auf einmal pro Objekt und nicht pro Klasse geführt? Das muss doch irgendwie auch anders gehen! Also ab in den Frickelmodus:

    template< typename Class >
    class InterfaceContainerStatic : public InterfaceContainer
    {
    public:
        std::set< std::wstring > GetInterfaceNames() override
        {
            std::set< std::wstring > result;
            for( auto it = interfaces_.begin(); it != interfaces_.end(); ++it ) {
                result.insert( it->first );
            }
            return std::move(result);
        }
    
        Interface* GetInterface( const std::wstring& name )
        {
            auto it = interfaces_.find( name );
            if( it != interfaces_.end() ) {
                return (*(*it).second)( this );
            } else return 0;
        }
    
        typedef Interface* (*CastFunctionType)( InterfaceContainerStatic* );		
    protected:
        static void RegisterInterfaceCaster( const std::wstring& name, CastFunctionType func )
        {
            interfaces_[name] = func;
        }
    
        static std::map< std::wstring, CastFunctionType > interfaces_;
    };
    
    template< typename SourceClass >
    std::map< std::wstring, typename InterfaceContainerStatic<SourceClass>::CastFunctionType > InterfaceContainerStatic<SourceClass>::interfaces_;
    

    Jetzt wird die Liste der Schnittstellen wunderbar pro Klasse geführt, aber wie schaffen wir es nun, in der GetInterface-Methode auf die Schnittstellen-Zeiger zu kommen? Da muss uns wohl eine Pro-Schnittstellen-Funktion helfen:

    template< typename SourceClass, typename InterfaceClass >
    class InterfaceStatic : public InterfaceClass, public virtual InterfaceContainerStatic< SourceClass >
    {
    public:
        InterfaceStatic() {
            register_object;
        }
    
    private:
        struct RegisterMe {
            static Interface* CastContainerToThis( InterfaceContainerStatic<SourceClass>* ptr )
            { 
                return dynamic_cast<InterfaceStatic*>(ptr);
            }
    
            RegisterMe() 
            { 
                RegisterInterfaceCaster( InterfaceClass::GetNameStatic(), &CastContainerToThis );
            }
        };
    
        static RegisterMe register_object;
    };
    
    template< typename SourceClass, typename InterfaceClass >
    typename InterfaceStatic<SourceClass,InterfaceClass>::RegisterMe InterfaceStatic<SourceClass,InterfaceClass>::register_object;
    

    Und hier wären wir nun schon beim Problem angekommen. Mir fällt wirklich kein anderer Weg ein, um von einem InterfaceContainerStatic* auf ein InterfaceStatic* zu kommen, als dynamic_cast. static_cast fällt leider wegen der virtuellen Vererbung aus. Nebenbei ist der Quellecode zumindest für mich auch nicht mehr unbedingt auf den ersten Blick 100%ig klar.

    Wisst ihr, ob und wie man das, was ich erreichen möchte, eleganter bewerkstelligen könnte?

    Viele Grüße,
    Michael



  • Was willst du genau erreichen? Klassen registrieren, aber sollen die mit bestimmten Zusatzinformationen (z.B. Vererbungsrelation) abgespeichert werden? Bei std::map<std::wstring, Interface*> verweisen Value-Einträge ja immer noch auf Objekte (und nicht auf Typen)... Wieso?

    Müssen die Reflection-Klassen so intrusiv sein oder könnte man sie grundsätzlich auch unabhängiger, mit weniger Vererbung etc. definieren?



  • Normalerweise leidet C++ unter dem Versuch, es zu Java zu machen.

    Was genau Du erreichen willst, erkenne ich nicht. Aber ich vermute, das sollte nicht zur Laufzeit geschehen. Hast Du "modern c++ design" gelesen?



  • Hallo!

    Das ganze entstand mit dem Hintergedanken, es bei "Selektionen" zu verwenden. Ich möchte, dass der Benutzer einen Satz von (u.a. unterschiedlichen) Objekten selektieren kann und allen Modulen die Möglichkeit geben, über die Selektion alles in Erfahrung zu bringen, was sie benötigen. Das Eigenschaften-Fenster schaut, ob das oder die derzeitig selektierten Objekte (von denen es überhaupt nichts weiß, keinen Typ, keinen gar nix, außer dass es Schnittstellen bereit hält über meine beschriebenen Klassen) das IProperties-Interface anbietet und holt sich darüber alles benötigte.
    Das grafische Fenster schaut, ob die Objekte eventuell verschiebbar sind (IMovable) und wenn alle selektierten Objekte die Schnittstelle unterstützen, bietet es dem Benutzer diese Eingabemöglichkeit an.
    Der Kommando-Manager schaut, ob die Objekte alle eine Copy-Schnittstelle anbieten und wenn ja, kann der Benutzer die Dinge kopieren.
    Das alles möchte ich damit umsetzen und vor allem ohne eine monolithische Mega-Basis-Klasse, ala CWnd oder sowas.

    Ich habe Modern C++ Design aus diesem Grund aufgeschlagen heute Nacht und irgendwie hatte ich das Gefühl, dass man das fortgeschrittenere Visitor-Pattern auch irgendwie zu solchen Zwecken einsetzen könnte. Aber auch mit etlichen Minuten nachgedenke habe ich es einfach nicht vor mir "gesehen", als würde das beschriebene zwar im Gegensatz zu meinem Ansatz super in C++ reinpassen, dabei aber total das verschleiern, wie ich über die Interaktionen zwischen den einzelnen Komponenten nachdenke.

    MfG,
    Michael

    PS: War der Threadtitel etwas verwirrend? 🙂

    PPS: Hier mal ein Schnipsel des "client"-codes:

    class NodeWidget : public widgets::FrameWidget, public Node::ViewInterface
        , utility::exp::InterfaceStatic< NodeWidget, utility::exp::IMove >
        , utility::exp::InterfaceStatic< NodeWidget, utility::exp::ITest >
    {
        // ...
    }
    

    Durch die gezeigte Ableitung sind die Interfaces automatisch durch die InterfaceContainer-Basisklasse ansprechbar.



  • Ich bin's wieder!
    Also ich habe mir jetzt mal Gedanken darüber gemacht, wie man das mit dem Modern C++ azyklischen Visitors erledigen könnte:

    #include <iostream>
    
    class Interface1 {};
    class Interface2 {};
    
    class VisitorBase
    {
    public:
        virtual ~VisitorBase() {}
    };
    
    class Visitable
    {
    public:
        virtual void Accept( VisitorBase& b ) = 0;  
    };
    
    template< typename Interface >
    class Visitor : public virtual VisitorBase
    {
    public:
        virtual void Visit( Interface& i ) = 0;
    };
    
    // Beispielklassen
    class MyVisitor
        : public Visitor<Interface1>, public Visitor<Interface2>
    {
        void Visit( Interface1& ) { std::cout << "Found Interface1\n"; }
        void Visit( Interface2& ) { std::cout << "Found Interface2\n"; }
    };
    
    class MyObject : public Visitable, public Interface1, public Interface2
    {
    public:
        void Accept( VisitorBase& b )
        {
            if( Visitor<Interface1>* p = dynamic_cast< Visitor<Interface1>* >( &b ) ) {
                p->Visit( *this );
            }
    
            if( Visitor<Interface2>* p = dynamic_cast< Visitor<Interface2>* >( &b ) ) {
                p->Visit( *this );
            }
        }
    };
    
    int main()
    {
    	MyObject obj;
    	MyVisitor vis;
    
    	obj.Accept( vis );
    
    	return 0;
    }
    

    Das ist natürlich ziemlich typsicher und benutzt das Visitor(tm)-Pattern. Allerdings muss man jetzt die Visit-Methode aller Visitable-abgeleiteten Objekte "handcraften" für alle Schnittstellen (Also an insgesamt mehr als 3 Stellen etwas verändern, wenn man den Klassen eine Schnittstelle hinzufügt).

    Zudem sehe ich jetzt auch nicht 100%ig den Vorteil zu der folgenden Lösung:

    class Object
    {
    public:
        template< typename T > T* QueryInterface()
        {
            if( T* p = dynamic_cast<T*>( this ) ) {
                return p;
            } else return 0;
        }
    
    public:
        virtual ~Object() {}
    };
    

    Was meint denn ihr dazu?

    Viele Grüße,
    Michael



  • Mit den Visitoren hatte ich immer etwas Mühe. Ich wollte sie schon ab und zu einsetzen, aber meist war für zu wenig Flexibilität zu viel Boilerplatecode notwendig... Bei dir gefällt mir das dynamic_cast nicht, so kannst du ja trotzdem nur Visitoren anwenden, die für MyObject schon bekannt sind. Ausserdem hast du eine manuelle Typunterscheidung, das ist selten gut.

    Sehe ich das richtig, dass du im Prinzip den Visitor mit allen Interfaces, von denen MyObject erbt, aufrufen willst?

    Mit statischer Polymorphie wäre das wohl einfacher, aber wahrscheinlich brauchst du dynamische.

    class MyObject : public Interface1, public Interface2
    { 
    public:
        template <typename F> 
        void Accept(F fn)
        {
            fn(*this); // fn kann auf is_base_of testen
        }
    };
    


  • Hallo Nexus!

    Sobald die Klase MyObject vom Interface1 ableitet, kennt sie natürlidch die Visitor<Interface1> Klasse (sofern sie das Visitor-Template kennt), die Implementationen braucht sie ja nicht zu kennen, weil es ja bloß eine abstrakte Basisklasse ist.
    Der Dynamic-Cast ist sozusagen ein Hack des Visitor-Patterns, damit man nicht alle (Interface-)Typen in den konkreten Visitoren kennen muss, sondern nur die bestimmten, die einen dort interessieren.
    Aber du hast natürlich Recht, das Visitor-Pattern war ja ursprünglich mal dazu gedacht, um diese Typ-Switch-Blöcke zu vermeiden, nun verwende ich es und habe dennoch welche!

    Statische Polymorphie kommt bei mir leider nicht in Frage.

    Irgendwie bekomme ich allmählich den Eindruck, dass die einfachste und direkteste Lösung die beschriebene "QueryInterface"-Methode ist und alle anderen Lösungen, die ich genannt habe, diesen dynamic_cast einfach nur verstecken oder (nicht 100% typsicher wegen id oder namenskollisionen) umschiffen.
    dynamic_cast ist wiederum auch nicht so 100%ig das Wahre, weil wenn ich mal eine Wrapper-Klasse für in C geschriebene Komponenten oder soetwas basteln wollte, müsste ich diese ja von allen Schnittstellen ableiten und könnte überhaupt nicht mehr zur Laufzeit bestimmen, welche Schnittstellen eventuell eben _nicht_ angeboten werden sollen.
    Irgendwie ist das alles gar nicht so einfach.



  • So, jetzt habe ich mich nochmal hingesetzt und diese Sache halbwegs vernünftig implementiert!

    visitor.h

    #pragma once
    
    namespace utility {
    
    	// =====================================================================================================
    	// class VisitorBase
    	// All visitors which shall be accepted by class Visitable's Accept member must derive from this class.
    	// Provides a virtual destructor for supplementing the objects with RTTI.
    	// =====================================================================================================
    
    	class VisitorBase
    	{
    	public:
    		virtual ~VisitorBase() {}
    	};
    
    	// =========================================================================================================
    	// class Visitable
    	// All visitable classes must (possibly indircetly, see class VisitableInterfaces and class InterfaceHelper)
    	// derive from this class.
    	// =========================================================================================================
    
    	class Visitable
    	{
    	public:
    		virtual void Accept( VisitorBase& b ) = 0; 	
    	};
    
    	// =========================================================================================================
    	// class Visitable
    	// All class which shall provide the interface reflection mechanism should derive (directly or indirectly,
    	// see class InterfaceHelperfrom) from this class.
    	// =========================================================================================================
    
    	class VisitableInterfaces : public Visitable
    	{
    	protected:
    		template< typename InterfaceType, typename ClassType >
    		void InterfaceVisit( VisitorBase& b, ClassType* this_ ) {
    			if( Visitor<InterfaceType>* p = dynamic_cast< Visitor<InterfaceType>* >( &b ) ) {
    				p->Visit( *this_ );		
    			}
    		}
    	};
    
    	// ===================================================================
    	// template class Visitor
    	// Derive your typed visitors from one or more classes of this type.
    	// ===================================================================
    
    	template< typename T >
    	class Visitor : public virtual VisitorBase
    	{
    	public:
    		 virtual void Visit( T& i ) = 0;
    	};
    
    	// ==============================================================================================================
    	// template class InterfaceHelper
    	// Derive your interfaces through this class rather than directly to provide interface reflection for your class.
    	// ==============================================================================================================
    
    	template< typename InterfaceType, typename SharedBase = VisitableInterfaces >
    	class InterfaceHelper : public InterfaceType, public virtual SharedBase
    	{
    	};
    
    	// =========================================================
    	// template class InterfaceVisitor
    	// Tries to visit a given Interface of a Visitable-object.
    	// =========================================================
    
    	template< typename T >
    	class InterfaceVisitor : public Visitor< T >
    	{
    	public:
    		InterfaceVisitor() : interfacePtr_(0) {}
    
    		T* GetInterface() const {
    			return interfacePtr_;
    		}
    
    	private:
    		virtual void Visit( T& interfaceRef ) {
    			interfacePtr_ = &interfaceRef;
    		};
    
    		T* interfacePtr_;
    	};
    
    	// ===========================================================================================================
    	// QueryInterface
    	// Tries to query a visitable object for a given interface. Returns 0 if the object does not support interface
    	// reflection for this interface.
    	// ===========================================================================================================
    
    	template< typename InterfaceType >
    	InterfaceType* QueryInterface( VisitableInterfaces& obj )	// Could as well take a plain Visitable-reference.
    	{
    		InterfaceVisitor< InterfaceType > visitor;
    		obj.Accept( visitor );
    		return visitor.GetInterface();
    	}
    
    }
    

    main.cpp

    #include <iostream>
    #include "visitor.h"
    
    class Interface1 {};
    class Interface2 {};
    
    class MyClass
    	: public utility::InterfaceHelper< Interface1 >,
    	  public utility::InterfaceHelper< Interface2 >
    {
    public:
    	void Accept( utility::VisitorBase& b )
    	{
    		InterfaceVisit<Interface1>( b, this );
    		InterfaceVisit<Interface2>( b, this );
    	}
    };
    
    int main()
    {
    	MyClass obj;
    
    	if( utility::QueryInterface<Interface1>( obj ) ) {
    		std::cout << "Found Interface1\n";
    	}
    
    	if( utility::QueryInterface<Interface2>( obj ) ) {
    		std::cout << "Found Interface2\n";
    	}
    
    	return 0;
    }
    

    Im Gegensatz zu der anderen Variante, nämlich obj per dynamic_cast zu den Schnittstellen zu casten, habe ich folgende Vorteile:
    - der dynamic_cast findet in der Visitor-Klassenhierarchie statt, welche im Zweifel wesentlich beschränkter ist (->schneller).
    - die Klassen, welche von Schnittstellen ableiten, haben selber die Kontrolle darüber, welche Schnittstellen sie nach außen verfügbar machen.

    Und im Gegensatz zu meiner originalen Lösung:
    - Typsicher immer eigenen Quelltext, keine Namens- oder ID-Kollisionen.

    Wenn aber jemand begründete Zweifel an dieser Umsetzung oder an der ganzen Sache mit Gegenbeispielen, Problem usw. hat, dann möge er sich bitte bitte melden! Man hört ja immer: benutze "nie" dynamic_cast, schlechtes Design etc. Aber mir fällt für die Komponentenbasierte Entwicklung im Moment nichts besseres ein.

    Viele Grüße,
    Michael



  • Was ich z.B. noch etwas unschön finde, ist die Accept() -Methode, die man immer schreiben muss und in der man alle Interfaces manuell aufrufen muss. Könnte InterfaceHelper nicht das entsprechende Interface in einer Liste innerhalb der Klasse SharedBase speichern, und eine Accept() -Methode anbieten, die alle Interfaces besucht? Du hast zwar dann den statischen Typen der abgeleiteten Klasse nicht mehr, von daher wäre wohl doch eine kleine Funktion notwendig, die this an SharedBase::Accept() weiterleitet.

    Generell kommt mir dein Design etwas kompliziert vor, vielleicht muss das aber auch so sein. Grundsätzlich zu den Selektionen: Können deine Objekte nicht einfach virtuelle Funktionen haben, die eine entsprechende Eigenschaft zurückgeben? Falls sie die Eigenschaft nicht haben, geben sie einen Nullzeiger zurück (evtl. als default-implementierte virtuelle Funktion). Oder kommen da häufig neue Eigenschaften hinzu?

    class Object
    {
        public:
            virtual ~Object();
    
            virtual MovableInfo*  GetMovableInfo() const = 0;
            virtual PropertyInfo* GetPropertyInfo() const = 0;
            virtual CopyInfo*     GetCopyInfo() const = 0;
    };
    


  • Nexus schrieb:

    Was ich z.B. noch etwas unschön finde, ist die Accept() -Methode, die man immer schreiben muss und in der man alle Interfaces manuell aufrufen muss. Könnte InterfaceHelper nicht das entsprechende Interface in einer Liste innerhalb der Klasse SharedBase speichern, und eine Accept() -Methode anbieten, die alle Interfaces besucht? Du hast zwar dann den statischen Typen der abgeleiteten Klasse nicht mehr, von daher wäre wohl doch eine kleine Funktion notwendig, die this an SharedBase::Accept() weiterleitet.

    Ja, da könnte man das wohl noch etwas "automatisieren", wenn mein keine genaurere Kontrolle benötigt. Das Überschreiben der Visit-Methode wird einem aber leider niemals erspart bleiben, wie du schon anmerkst. Aber das ist ja schonmal nur eine kleine Erweiterung die nichts am grundsätzlichen Vorgehen verändert.

    class Object
    {
        public:
            virtual ~Object();
    
            virtual MovableInfo*  GetMovableInfo() const = 0;
            virtual PropertyInfo* GetPropertyInfo() const = 0;
            virtual CopyInfo*     GetCopyInfo() const = 0;
    };
    

    Das ist natürlich eine der gangbare Alternativen, wenn man partout auf dynamic_cast verzichten möchte. Allerdings wird einem ja die unnötige Verbindung klar, wenn man sieht, dass alle Subsysteme, die auf auf Movable, Property und Copy bauen neu kompiliert werden müssen, weil ich ein anderes Subsystem dazu bastle, das eine neue Schnittstelle definiert. Das ist natürlich kein Showstopper, ich füge auch nicht ständig was neues hinzu, aber es ist Basisklassenbloat und fühlt sich irgendwie nicht richtig an.

    Noch eine Alternative wäre, wenn sich die Objekte selber bei den Subsystemen registrieren (Das könnten ja auch Helferklassen automatisieren), hier ist mir nur nicht so ganz klar, ob sich das mit dem Selektieren dann noch so einfach gestaltet. Dann müssten die einzelnen Subsysteme nur irgendwie vom Selektions-Manager wissen und irgendwie ihre Schnittstellen-Listen mit der Selektionsliste vergleichen können. Hrmmm. Außerdem müsste man dann dem Konstruktor eventuell irgendeinen Kontext mitgeben, damit die Helferklassen wissen, von welchem Schnittstellen-Manager sie gemanaged werden, sollte es mehrere davon geben können, womit alle schnittstellenhelfer neu kompiliert werden müssten, wenn ich eine Schnittstelle hinzufüge.



  • Ich habe einmal deinen Automatisierungsvorschlag aufgegriffen und habe folgenden Erweiterungsklassen geschrieben:

    template< typename T1, typename T2, typename T3 >
    	class InterfaceHelperEnhanced;
    
    	template< typename Class >
    	class VisitableInterfacesEnhanced : public VisitableInterfaces
    	{
    	protected:
    		void DescribeInterfaces( VisitorBase& vis, Class* this_ ) const
    		{
    			for( auto it = descriptors_.cbegin(); it != descriptors_.cend(); ++it ) {
    				(*(*it))( vis, this_ );
    			}
    		}
    
    	private:
    		typedef void (*InterfaceDescriptorFunc)( VisitorBase& vis, Class* this_ );
    		static std::list< InterfaceDescriptorFunc > descriptors_;
    
    		template< typename T >
    		friend class InterfaceHelperEnhanced<Class,T,VisitableInterfacesEnhanced>::Register;
    	};
    
    	template< typename Class >
    	std::list< typename VisitableInterfacesEnhanced<Class>::InterfaceDescriptorFunc > VisitableInterfacesEnhanced<Class>::descriptors_;
    
    	template< typename Class, typename InterfaceType, typename SharedBase = VisitableInterfacesEnhanced<Class> >
    	class InterfaceHelperEnhanced : public InterfaceType, public virtual SharedBase
    	{
    	public:
    		InterfaceHelperEnhanced()
    		{
    			reg_;	// Force static instantiation.
    		}
    
    	private:
    		class Register {
    		public:
    			Register() {
    				SharedBase::descriptors_.push_back( &Register::Describe );
    			}
    
    			static void Describe( VisitorBase& visitor, Class* this_ ) {
    				InterfaceVisit< InterfaceType >( visitor, this_ );
    			}
    		};
    
    		static Register reg_;
    	};
    
    	template< typename Class, typename InterfaceType, typename SharedBase >
    	typename InterfaceHelperEnhanced< Class, InterfaceType, SharedBase >::Register InterfaceHelperEnhanced< Class, InterfaceType, SharedBase >::reg_;
    

    Jetzt sieht der Client-Code folgendermaßen aus:

    class Interface1 {};
    class Interface2 {};
    
    class MyClass
    	: public utility::InterfaceHelperEnhanced< MyClass, Interface1 >,
    	  public utility::InterfaceHelperEnhanced< MyClass, Interface2 >
    {
    public:
    	void Accept( utility::VisitorBase& b )
    	{
    		DescribeInterfaces( b, this );
    	}
    };
    

    Ist natürlich jede Menge kryptischer Code in der Bibliothek, andererseits vergisst man auf diese Weise keine Schnittstellen, und da das nur auf derm bereits bestehenden aufbaut, besitzt die Sache noch dieselben Eigenschaften. Insbesondere fiel mir auf, dass man das ganze relativ einfach in die Fat-Base-Class Variante von dir umwandeln kann, sollte es nötig werden.



  • Jetzt hast du aber durch den ersten Templateparameter von InterfaceHelperEnhanced auch den statischen Typen von MyClass in der Basisklasse. Könntest du dann nicht Accept() direkt dort anbieten?

    Oder soll Accept() in weiteren abgeleiteten Klassen überschrieben werden können? In dem Fall sollte der erste Templateparameter unnötig sein. Und dann würde ich wahrscheinlich sogar ein Makro für die Accept() -Implementierung schreiben.

    Übrigens würde ich mir für "InterfaceHelperEnhanced" einen anderen Namen ausdenken, oder zumindest das "enhanced" weglassen. Nichts ist mühsamer als lange Bezeichner, die nicht einmal aussagekräftig sind 😉



  • Ja, die Klassen sind ja jetzt nur testweise draufgepropft, ich werde die nicht-enhanced-teile dann wohl eher in einen detail-namespace verfrachten und die neuen wie die alten benennen^^

    Ich kann in InterfaceHelperEnhanced nicht Accept überschreiben, weil die Überschreibung bei mehreren Interfaces dann mehrdeutig würde. Und in VisitableInterfacesEnhanced kann ich this nicht zu MyClass* hochcasten, weil ein Cast über eine virtuelle Basisklasse nicht erlaubt ist. Oder findest du vielleicht einen Weg? Ich hatte mir schon bei meiner ersten Lösung von Seite 1 die Zähne daran ausgebissen.

    Den MyClass-Parameter brauche ich ja in jedem Fall, damit die Liste der InterfaceVisit-Aufrufe statisch pro Klasse geführt wird, anstatt für jedes Objekt der Klasse einzeln eine Liste zu führen.


Anmelden zum Antworten