Deklaration und Definition in zwei unterschiedliche geerbte Klassen trennen.



  • Guten Tag,
    obwohl ich hier neu bin, komme auch sogleich mit einem für mich großem Problem(Und ich hoffe auch ein wenig für euch, damit ich mich selbst schon mal als einen einigermaßen erfahrenen Programmierer einschätzen kann. 🙂 ).

    Hier zu meinem Problem:
    Ich habe eine Template-Klasse Inter, welche ein Interface darstellt und eine Methode foo(UnknownType) deklariert. Des weiteren erbt diese Klasse wieder von einer anderen Spezialisierung seiner Klasse(Inter). Dadurch enthält diese Klasse später viele abstakte foo-Methoden, welche sich nur durch ihren Übergabeparametertyp unterscheiden. Als Templateargument übergibt man der Klasse eine Typliste.
    Da die Anzahl der Methoden erst zur Kompilierungszeit bekannt ist, muss ich auch die Implementierung variabel halten. Dies habe ich mit einer weiteren Klasse Defin versucht, welche fast genauso aufgebaut ist, wie die Klasse Inter, nur dass die Methode nicht abstrakt, sondern vollständig definiert ist.
    Diese beiden erstellten Klassen versuche ich nun in einer nächsten Klasse Merge zusammenzuführen, in dem sie von beiden Klassen erbt. Leider klappt dies scheinbar so nicht. Sobald ich nämlich versuche eine Instanz von Merge zu erzeugen, wird mir gesagt, dass die Klasse abstrakt sei.
    Als Test habe ich mal folgendes Konstrukt geschrieben, welches leider auch nicht funktioniert:

    class Inter
    {
    public:
    	virtual ~Inter() {}
    	virtual void foo() = 0;
    };
    
    class Defen
    {
    public:
    	virtual ~Defen() {}
    	virtual void foo() { cout << "Defen"; }
    };
    
    class Merged: public Defen, public Inter
    {
    };
    

    Daher ist nun meine Frage, ob es da eine Möglichkeit gibt, dies auf eine andere Weise zu implementieren oder ob ich einfach nur irgendwo einen Fehler gemacht habe.
    Der Grund, warum ich das alles eigentlich mache ist, weil man in C++ keine Template-Methoden virtuell machen kann, was wohl auch seinen Grund hat ...

    Gruß, Mickeyman.



  • Mickeyman schrieb:

    Diese beiden erstellten Klassen versuche ich nun in einer nächsten Klasse Merge zusammenzuführen, in dem sie von beiden Klassen erbt. Leider klappt dies scheinbar so nicht. Sobald ich nämlich versuche eine Instanz von Merge zu erzeugen, wird mir gesagt, dass die Klasse abstrakt sei.

    Ist auch kein Wunder, schliesslich ist die Basisklasse Inter nach wie vor abstrakt. Dass nebenbei noch von einer anderen Klasse geerbt wird, die zufällig das gleiche Interface hat, tut nichts zur Sache.

    Ich denke, du möchtest eher Defen von Inter ableiten, um die in Inter rein virtuell deklarierten Methoden zu überschreiben. Ich verstehe nicht ganz, worauf du mit der variablen Implementierung zur Compilezeit hinauswillst, aber könnte mir denken, dass du damit die vom Interface erbende Klasse leicht austauschen willst (was ja durch einfache Vererbung ohne weiteres möglich wäre). Auf jeden Fall stellt sich aber die Frage, ob Merge überhaupt notwendig ist.



  • Danke für deine Antwort.
    Der Grund ist der, dass ich eine Schnittstelle mit ganz vielen exchanger-Methoden habe, die sich nur durch einen Übergabeparametertyp unterscheiden. Auch die Implementierungen dessen sind nahezu alle gleich. Derzeit habe ich auch redundant jede Methode einzeln aufgeschrieben. Um diese Redundanz zu vermeiden versuche ich jetzt jedoch, es mit generischer Programmierung zu umgehen. Denn es könnten jederzeit neue Typen hinzukommen, sowie Änderungen an den bestehenden Methoden vorgenommen werden. Vor allem die Änderungen sind derzeit extrem aufwändig.
    Daher hier mal ein etwas konkreteres Beispiel, das ich nach dem neuen Muster von Nexus gemacht habe. Leider gibt es dabei das Problem, dass er nur die Methode aus der ersten Basisklasse erkennt. Des weiteren gibt es noch das Problem, dass das eigentliche Interface für den Compiler nicht mehr klar erkennbar ist.
    Hier der Code:

    template<typename typelist>
    class GenericInterface;
    
    template<typename T1, typename T2>
    class GenericInterface< Typelist<T1,T2> >: public GenericInterface<T2>
    {
    public:
    	typedef T1 CurrType;
    
    public:
    	virtual ~GenericInterface() {}
    	virtual void exchange(T1 value) = 0;
    };
    
    template<>
    class GenericInterface< Nulltype >
    {
    };
    
    //---------------------------------------------------
    
    template<typename typelist>
    class Exchanger;
    
    template<typename T1, typename T2>
    class Exchanger< Typelist<T1,T2> >: public GenericInterface< Typelist<T1, Nulltype> >, public Exchanger<T2>
    {
    public:
    	typedef T1 CurrType;
    
    public:
    	virtual ~Exchanger() {}
    	void exchange(T1 value)
    	{
    		std::cout << value << std::endl;
    	}
    };
    
    template<>
    class Exchanger< Nulltype >
    {
    };
    
    //---------------------------------------------------
    //---------------------------------------------------
    
    int main ()
    {
    	typedef Typelist<char, Typelist<bool, Typelist<int, Nulltype> > > MyTypelist;
    	typedef GenericInterface<MyTypelist> SpezInterface;
    	typedef Exchanger<MyTypelist> SpezExchanger;
    
    	SpezExchanger ex;
    	// Leider erkennt er hier nur exchange(char).
    	ex.exchange(1324); // Warning
    
    	// Wie oben schon beschrieben, erkennt er hier das Interface nicht.
    	SpezInterface* interface1 = &ex; // Error
    
    	return 0;
    }
    

    Ich hoffe, dass es nun ein wenig verständlicher geworden ist.
    Es könnte natürlich auch sein, dass es dazu keine Lösung gibt. Sollte dies derzeit wirklich nicht lösbar sein, dann würde ich gerne noch wissen, ob es denn im neuen C++0x machbar wäre?

    Gruß, Mickeyman.



  • Mickeyman schrieb:

    Um diese Redundanz zu vermeiden versuche ich jetzt jedoch, es mit generischer Programmierung zu umgehen.

    Richtiger Ansatz, Codeduplizierung zu vermeiden ist immer gut. 👍

    Also zum Problem. Ich habe mit deinem Code herumexperimentiert und denke, nun sollte es laufen. 🙂

    Ein Problem bei dir war, dass jede exchange() -Funktion diejenige der Basisklasse gerade wieder verdeckte. Mittels using kann das aufgelöst werden. Dazu muss aber eine entsprechende Methode in der Basisklasse vorhanden sein, auch in der letzten Instanziierung. Um dies zu erreichen, habe ich eine Templatespezialisierung für Typelist<T1, Nulltype> eingerichtet und dort kein using mehr benutzt. Die Spezialisierung GenericInterface<NullType> entfällt somit, da leere Typlisten schon wegen Exchanger nicht erlaubt sind.

    template <typename T1, typename T2> 
    class GenericInterface< Typelist<T1,T2> > : public GenericInterface<T2> 
    { 
    public:
    	using GenericInterface<T2>::exchange;
        virtual void exchange(T1 value) = 0; 
    }; 
    
    template <typename T1>
    class GenericInterface< Typelist<T1,Nulltype> >
    {
    public:
    	virtual ~GenericInterface() {};
    	virtual void exchange(T1 value) = 0;
    };
    

    Soviel dazu. Nun aber zum interessanten Teil. In deinem Code hast du in jeder Instanziierung von Exchanger von GenericInterface<T1,Nulltype> abgeleitet. Das ist einerseits unnötig, andererseits kann so die Vererbungsbeziehung nicht hergestellt werden, weil es sich eben um eine andere Klasse handelt. Bedenke, dass in deiner Implementierung das Template GenericInterface jeweils mit einer 1-elementigen Typliste instanziiert wird.

    Doch wie kann man diesem Problem begegnen? Naheliegend wäre es, nur einmal von GenericInterface abzuleiten, dafür mit der gesamten Typliste. Es stellt sich jedoch die Frage, wann dies geschehen soll. Bei der ersten Instanziierung, also in der obersten Rekursionsebene? Sicherlich nicht, denn die Basisklasse muss ja bekannt sein, wenn die Methoden überschrieben werden. Da die Instanziierungen für jeden konkreten Typ ( char , bool , int in deiner Typliste) von GenericInterface erben müssen, kommt eigentlich nur eine Möglichkeit in Frage: GenericInterface muss am Ende der Vererbungskette stehen. Da die Typliste mit zunehmender Vererbungstiefe immer kleiner wird und am Schluss leer ist, muss man sich jedoch überlegen, mit welcher Typliste man nun GenericInterface instanziiert. Dazu habe ich einen zweiten Template-Parameter mit einer unveränderlichen Typliste gewählt, welcher die Originalinformation durch alle Klassen trägt.

    template<typename TL> 
    class Exchanger; 
    
    template <typename T1, typename T2> 
    class Exchanger< Typelist<T1,T2> > : public ExchangerImpl< Typelist<T1,T2>, Typelist<T1,T2> >
    { 
    public: 
        typedef T1 CurrType;
    };
    

    Wie du siehst, findet der eigentliche Kern nicht in Exchanger statt, sondern in ExchangerImpl . Ich habe diesen Weg gewählt, weil so die öffentliche Schnittstelle (in diesem Falle das Template Exchanger ) nur relevante Templateparameter enthält. ExchangerImpl sieht so aus:

    template <typename RemainingTL, typename WholeTL>
    class ExchangerImpl;
    
    template <typename T1, typename T2, typename WholeTL>
    class ExchangerImpl< Typelist<T1,T2>, WholeTL > : public ExchangerImpl< T2, WholeTL >
    {
    public:
    	using ExchangerImpl<T2, WholeTL>::exchange;
    
    	void exchange(T1 value) 
        { 
            std::cout << value << std::endl; 
        } 
    };
    
    template <typename WholeTL>
    class ExchangerImpl< Nulltype, WholeTL > : public GenericInterface< WholeTL >
    { 
    };
    

    Ich hoffe, ich konnte dir bei deinem Problem helfen. Ich muss aber sagen, dieser Thread enthält für mich eine der interessanteren Problemstellungen dieses Forums. Template-Metaprogrammierung sieht man hier eher selten. 😉



  • Das ist ja super! Klappt wunderbar.
    Danke für deine so schnelle Hilfe. Das wird wohl mein Codevolumen so um die 80% und steigend verringern! Und von der Wartbarkeit braucht man erst gar nicht zu sprechen. 🙂

    Vielleicht werde ich dir zu liebe irgendwann wieder etwas über Metaprogrammierung posten. 🙂


  • Mod

    Das Ganze kann noch etwas verfeinert werden, indem die eigentliche Implementation von den technischen Artefakten, die für die Vererbung gebraucht werden, getrennt wird:

    #include <boost/mpl/vector.hpp>
    #include <boost/mpl/inherit_linearly.hpp>
    #include <boost/mpl/empty_base.hpp>
    #include <boost/mpl/placeholders.hpp>
    
    template <typename T>
    class Exchanger
    {
    public:
        virtual ~Exchanger() {}
        virtual void exchange(T) {}
    };
    
    template <typename T1, typename T2>
    class ExchangerNode : public T1, public Exchanger<T2>
    {
    public:
        using T1::exchange;
        using Exchanger<T2>::exchange;
    };
    template <typename T>
    class ExchangerNode<boost::mpl::empty_base,T> : public Exchanger<T>
    {};
    
    template <typename T1,            typename T2 = boost::mpl::na, typename T3 = boost::mpl::na,
        typename T4 = boost::mpl::na, typename T5 = boost::mpl::na, typename T6 = boost::mpl::na,
        typename T7 = boost::mpl::na, typename T8 = boost::mpl::na, typename T9 = boost::mpl::na>
    class Interface : public boost::mpl::inherit_linearly<
        boost::mpl::vector<T1,T2,T3,T4,T5,T6,T7,T8,T9>,
        ExchangerNode<boost::mpl::placeholders::_1, boost::mpl::placeholders::_2> >::type
    {};
    
    int main()
    {
        Interface<wchar_t*,char*> i;
        i.exchange("");
        i.exchange(L"");
    }
    


  • Danke für die Alternative.
    Allerdings muss ich sagen, dass ich die nicht so ganz verstehe. Ich sehe nirgends die Vererbung von Interface. Es geht dort scheinbar eher anders herum. Das das Interface von der Implementierung erbt. Vielleicht irre ich mich da aber auch. Habe immer die Boost-Lib gemieden.
    Es dürfte da aber wohl noch ein Problem geben. Und zwar kann es bei mir so bis zu 100 verschiedene Typen geben. Ich glaube nicht, dass man so viele Templateparameter übergeben kann, sondern nur über eine Typliste.

    Gruß, Mickeyman.


Anmelden zum Antworten