Generische Typen...



  • Hallo,

    ich habe momentan 2 Probleme:

    1. Problem: Das Debuggen funktioniert nicht. Ich kann innerhalb der Deklaration meiner Mehtoden in der Source Fehler reinbauen und trotzdem wird ohne Fehler compilt...

    Header

    template<class Base> class B
    {
    public:
        virtual Base Create()=0;
        virtual bool IsClass(Base obj)=0;
    };
    
    template<class Base, class Class> class A: public B<Base>
    {	
    public:
        Base Create();
        bool IsClass(Base obj);
    };
    

    Source:

    #include "stdafx.h"
    #include "Factory.h"
    
    template<class Base, class Class> Base A<Base, Class>::Create() //hier werden Fehler noch erkannt
    {
        h //trotzdem kein Fehler!!!?!
        return Class();
    }
    

    2. Problem:
    Ich habe ja 2 generische Typen eingebunden. Diese sollen aber in Abhängigkeit zueinander stehen, also es soll gelten: (Class : Base). Wie setze ich das um?



  • Manche Compiler finden solche Fehler erst, wenn man das Template instanziiert (ich glaube MSVC z.B.).
    Lege mal ein konkretes Objekt über Dein Klassentemplate an. Dann sollte er eigentlich beim Compilieren meckern.

    PS: Templates kann man nicht in Header und Implementierung (also in .h und .cpp) aufteilen. Zum Zeitpunkt der Instanziierung eines Templates muss dieses vollständig bekannt sein.

    Zu 2.
    Willst a) Du prüfen, ob Class von Base geerebt hat, oder b) irgendwie erreichen dass Class plötzlich von Base erbt?

    Zu a) Das geht z.B. mit boost::is_base_of .
    Zu b) Das geht nicht.



  • Zum 1. Problem. Der Debugger hat mit dem compilieren nichts zu tun. Das sind zwei völlig verschiedene Sachen. Debuggern kannst du nur Sachen, die compiliert werden konnten. Dass der Code nicht angemängelt wird, liegt einfach da dran, dass er nicht compiliert wird. Du musst eine Template-Methode schon nutzen, damit der Compiler eine konkrete Funktion daraus machen kann, die dann auch compiliert werden könnte. Ein Template ist eine Vorlage, sprich etwas unfertiges, was der Compiler erst noch vervollständigen muss.



  • Absolute_nooby schrieb:

    Header

    template<class Base> class B
    {
    public:
        virtual Base Create()=0;
        virtual bool IsClass(Base obj)=0;
    };
    

    Das sieht relativ nutzlos aus. Bist Du Dir sicher, dass Create keinen Zeiger zurückgeben und IsClass keinen Zeiger (oder eine Referenz) entgegen nehmen soll? Beim IsClass würde ich noch zwei const s reinsetzen. Also,

    virtual bool IsClass(Base const& obj) const =0;
    

    Das sieht ehrlich gesagt so aus, als ob ein Java-Programmierer sich an C++ versucht.

    Source:

    #include "stdafx.h"
    #include "Factory.h"
    
    template<class Base, class Class> Base A<Base, Class>::Create() //hier werden Fehler noch erkannt
    {
        h //trotzdem kein Fehler!!!?!
        return Class();
    }
    

    Da es sich hier um eine Elementfunktion eines Klassentemplates handelt, muss die Definition sowieso im Header auftauchen. Dass Dein Compiler sich nicht über das h beschwert, ist ein schlechtes Zeichen bzgl Qualität Deines Compilers.

    2. Problem:
    Ich habe ja 2 generische Typen eingebunden. Diese sollen aber in Abhängigkeit zueinander stehen, also es soll gelten: (Class : Base). Wie setze ich das um?

    Gar nicht. Das ist unnötig. C++ Templates != Java Generics. Falls Du "Class:Base" erwartest und sich der Benutzer nicht daran hält, bekommst Du zur Compile-Zeit einen Fehler. Das resultiert zwar oft in einen schwer lesbaren Haufen von Fehlermeldungen, aber immerhin ist es ein Compile-Zeit-Fehler. Das einzige, was da verbesserungswürdig ist, ist die Sache mit den schwer lesbaren Fehlermeldungen zur Compile-Zeit. In C++0x könnte man dafür ein static_assert verwenden:

    #include <type_traits>
    
    ...
    
    template<class Base, class Class>
    class SomeFactory : public B<Base>
    {
    public:
        static_assert(std::is_base_of<Base,Class>::value,
            "I expect Base to be a base class of Class");
        static_assert(std::is_polymorphic<Base>::value,
            "I expect Base to be a polymorphic class");
    
        Base* Create();
        bool IsClass(Base const& obj) const;
    };
    

    Aber das ist noch nicht offiziell.



  • Ich nutze VS2008 SP1.

    1.Problem:
    Also muss ich die Funktionen einfach nur innerhalb meines headers deklarieren?

    2. Problem:
    Gut, dass ich das nicht explizit deklarieren muss. Das heißt also, dass beim Compilen ein Fehler auftauchen würde, falls diese Voraussetzung (Class : Base) nicht geschaffen ist (natürlich nur, falls diese Vererbung auf irgendeine Weise genutzt wird...)

    @krümelkacker:
    Fast richtig... ich versuche mich zwar an C++, komme aber aus der Welt des C# 😉
    Die IsClass braucht keine Referenz. Bei dem Create übergebe ich das Objekt dann per "return".
    Aber jetzt wo du es erwähnst: Ist es unsauber per return Objekte zurückzugeben? Wäre das Zurückgeben per Zeiger (oder sogar einen Zeiger auf den Zeiger) besser?



  • Absolute_nooby schrieb:

    1.Problem:
    Also muss ich die Funktionen einfach nur innerhalb meines headers deklarieren?

    Du solltest die Elementfunktionen von Klassen-Templates innerhalb des Headers definieren, wenn das Klassentemplate auch im Header definiert wurde. Siehe Dein C++ Lieblingsbuch unter dem Stichwort "ODR" (one definition rule). Solltest Du keins besitzen, besorg Dir eins. Es gibt diverse Buchempfehlungen. Gutes Buch = große Zeitersparnis.

    Absolute_nooby schrieb:

    2. Problem:
    Gut, dass ich das nicht explizit deklarieren muss. Das heißt also, dass beim Compilen ein Fehler auftauchen würde, falls diese Voraussetzung (Class : Base) nicht geschaffen ist (natürlich nur, falls diese Vererbung auf irgendeine Weise genutzt wird...)

    Genau. Der ganze Template-Kram wird komplett zur Compile-Zeit behandelt. B<int> und B<double> sind zwei verschiedene Typen (auch zur Laufzeit!) aus derselben "Typfamilie".

    Absolute_nooby schrieb:

    Die IsClass braucht keine Referenz. Bei dem Create übergebe ich das Objekt dann per "return".
    Aber jetzt wo du es erwähnst: Ist es unsauber per return Objekte zurückzugeben? Wäre das Zurückgeben per Zeiger (oder sogar einen Zeiger auf den Zeiger) besser?

    Kann man so pauschal nicht sagen. Dein Code ist nicht besonders selbsterklärend. Du musst schon sagen, was Du eigentlich machen willst. Gerade als C#-Flüchtling und C++-Anfänger wirst Du versuchen typische C#-Designs nach C++ zu übertragen. Das wird aber selten gut gehen, da die Sprachen nicht viel gemein haben außer geschweifte Klammern (um's mal überspitzt zu formulieren).

    Aber so, wie es aussieht, ist das sehr wahrscheinlich falsch, wie Du es machst. Google mal nach c++ und slicing. Und falls Du für "Base" eine abstrakte Klasse einsetzt, wird es gar nicht erst kompilieren.



  • Jetzt bin ich echt verwundert... In dem Programmcode von oben funktioniert das Compilen nun richtig (ich habe die Deklaration im Header und rufe eine Instanz auf). Nun habe ich aber eine weitere Klasse in der gleichen Header Datei, die sich einfach nicht richtig compilen lässt...

    template<class Base> class C
    {
    public:
    	Base method()
    	{
    		h //kein Fehler?!
    	}
    };
    

    Aufrufen tue ich die Klasse auch:

    C<Sample_Class> test;
    

    @krümelkacker:
    Vielen Dank für den Slice Hinweis! Der ist sehr sehr hilfreich! Und du hast recht, so würde es falsch werden. Ich hätte wahrscheinlich ewig für die Suche der Fehlerursache gebraucht...



  • Absolute_nooby schrieb:

    Jetzt bin ich echt verwundert... In dem Programmcode von oben funktioniert das Compilen nun richtig (ich habe die Deklaration im Header und rufe eine Instanz auf).

    Du meinst wahrscheinlich "Definition".

    Absolute_nooby schrieb:

    Nun habe ich aber eine weitere Klasse in der gleichen Header Datei, die sich einfach nicht richtig compilen lässt...

    template<class Base> class C
    {
    public:
    	Base method()
    	{
    		h //kein Fehler?!
    	}
    };
    

    Aufrufen tue ich die Klasse auch:

    C<Sample_Class> test;
    

    Man ruft keine Klassen auf. Du instanziierst hier indirekt eine Spezialisierung eines Klassentemplates. Dabei werden aber nicht automatisch alle Elementfunktionen erzeugt. Das versucht der Compiler erst dann, wenn sie auch irgendwo benutzt werden:

    test.method();
    

    Spätestens hiermit sollte der Compiler Fehlermeldungen ausgeben. Dass er es nicht schon früher tut, ist aber wie gesagt ein schlechtes Zeichen bzgl Compiler-Qualität; denn, obwohl "Base" ja erstmal nur ein Platzhalter ist, kann ein guter Compiler den Funktionskörper trotzdem zumindest teilweise überprüfen -- also, ob syntaktisch alles richtig ist und ob Bezeichner (wie zB 'h'), die von den Template-Parametern nicht abhängen, gefunden werden können.

    Folgendes ist aber korrektes C++ und kompiliert garantiert:

    template<class T>
    class foo {
      int dings(T x) { return x.first; }
      void bums() {}
    }
    
    int main()
    {
      foo<int> f;
      f.bums();
    }
    

    Da foo<int>::dings nirgens aufgerufen wird, versucht der Compiler auch nicht die Funktion zu übersetzten. Syntaktisch ist das, was im Körper steht, aber korrekt und für T lässt sich etwas einsetzten, so dass "return x.first;" einen Sinn ergibt. C++ Templates != Java/C# Generics.

    Tu Dir einen Gefallen und bestell Dir eines (oder mehr) der empfohlenen C++ Bücher. Also, erstmal die Sprache richtig lernen (siehe Beginner/Introductory-Abschnitt), dann Best Practices nachlesen (siehe Beginner/Best_Practices-Abschnitt) und wenn Dich dann Templates noch interessieren und Du den Anspruch hast, sie komplett zu verstehen, dann hol Dir noch "C++ Templates: The Complete Guide". Ich kann das nur wiederholen: Gescheite Bücher zu diesen Themen sind unersetzbar. C++ hast Du viel schneller "drauf" mit als ohne solche Bücher. Und Zeit ist Geld. Die Zeit, die Du damit sparst, ist viel teurer als der Preis der Bücher.

    kk


Anmelden zum Antworten