eigene Klasse als Template-Parameter übergeben



  • Hallo liebe C++-Experten,
    ich möchte eine Art universellen Ressourcenmanager mit Templates programmieren. Der Manager hat eine Liste mit Zeigern zu den verwalteten Ressourcen. Jede Ressource soll wiederum einen Zeiger auf den Manager haben, der sie verwaltet.

    class CResource
    {
      private:
        uint16 nRefCount;
        CResourceManager* pMgr;
      public:
        friend class CResourceManager;
        // ein paar Funktionen
    };
    
    template<typename T>
    class CResourceManager
    {
      public:
        std::list<T*> pResources;
        // ein paar Funktionen
    };
    

    In dieser rohen Form sollen die Klassen später nicht zum Einsatz kommen, sondern nur abgeleitete Klassen. Das T soll also beliebige von CResource abgeleitete Klassen annehmen können.

    In Zeile 5 und 7 beschwert sich der Compiler, dass ich T keinen Typ zuweise. Das blöde ist, dass ich selbst nicht weiß, was ich dort in spitzen Klammern hinschreiben soll. Wenn CResource nicht abgeleitet werden würde, würde ich in Zeile 5 natürlich schreiben:

    CResourceManager<CResource>* pMgr;
    

    Doch ich möchte, dass eine von CResource abgeleitete Klasse CExampleResource einen Zeiger vom Typ CResourceManager<CExampleResource>* hat und eine Klasse CExample2Resource soll einen Zeiger vom Typ CResourceManager<CExample2Resource>* haben.

    Ist das irgendwie möglich?

    Vielen Dank schonmal im Voraus! 🙂



  • Mir gefällt das alles irgendwie nicht... Aber du könntest hier schon aus CResource eine Template Klasse machen. Irgendwie so:

    template <typename T>
    class CResource {
      CResourceManager<T>
    ...
    };
    
    class CExampleResource: public CResource<CExampleResource> {
    ...
    };
    

    Sowas nennt sich "Curiously recurring template pattern".

    Aber lass zumindest das "C" bei deinen Klassen weg, das macht kein Mensch mehr.



  • Beim CRTP aber aufpassen, zum Zeitpunkt der Instantiierung ist die abgeleitete Klasse unvollständig, die Base kann also auf keine Methoden etc. zugreifen und keine Objekte auf dem Stack erzeugen (wie sonst auch bei unvollständigen Typen).



  • Nathan schrieb:

    Beim CRTP aber aufpassen, zum Zeitpunkt der Instantiierung ist die abgeleitete Klasse unvollständig, die Base kann also auf keine Methoden etc. zugreifen und keine Objekte auf dem Stack erzeugen (wie sonst auch bei unvollständigen Typen).

    Ich verstehe nicht ganz, worauf bezieht sich das? Denn

    template<typename T>
    class base
    {
    public:
    	void function()
    	{
    		T t;
    		t.other_function();
    	}
    };
    
    class derived : public base<derived>
    {
    public:
    	void other_function() {}
    };
    
    int main()
    {
    	derived d;
    	d.function();
    }
    

    Ist ja gültig



  • Flashput schrieb:

    Nathan schrieb:

    Beim CRTP aber aufpassen, zum Zeitpunkt der Instantiierung ist die abgeleitete Klasse unvollständig, die Base kann also auf keine Methoden etc. zugreifen und keine Objekte auf dem Stack erzeugen (wie sonst auch bei unvollständigen Typen).

    Ich verstehe nicht ganz, worauf bezieht sich das? Denn

    ...
    

    Ist ja gültig

    Ja, ich hatte mich unklar ausgedrückt.

    template<typename T>
    class base
    {
    public:
       using some_t = decltype(T().some_function());
       using other_t = typename T::foo;
    };
    

    Das ist ungültig.



  • Ach ja, ok.



  • Vielen Dank für die schnellen Antworten! Mit CRTP funktioniert es nun.

    Mechanics schrieb:

    Mir gefällt das alles irgendwie nicht...

    Kannst du das irgendwie sachlich begründen oder war das nur eine Geschmacksäußerung?



  • Mr Train schrieb:

    Mechanics schrieb:

    Mir gefällt das alles irgendwie nicht...

    Kannst du das irgendwie sachlich begründen oder war das nur eine Geschmacksäußerung?

    Das ist eher eine Geschmacksäußerung. Ich kenn die Anforderungen und das Gesamtsystem nicht, deswegen kann ich auch keine qualifizierte Aussage drüber treffen, ob das hier nicht doch angebracht wäre.
    Das basiert einfach auf Erfahrung, und meiner Erfahrung gefällt das nicht 😉

    Ich kann jetzt auch nicht aus dem Stegreif formulieren, warum mir das nicht gefällt, ich versuch mal paar Punkte aufzuzählen:

    - Ableitung ist eine sehr starke Abhängigkeit und sowas ist schlecht und sollte man nach Möglichkeit grundsätzlich vermeiden.
    - Ich mag es auch nicht, wenn mir irgendwelche Frameworks irgendwelche Basisklassen vorschreiben, von denen ich ableiten muss. POC Klassen sind besser. Damit kann man dann das Framework einfacher austauschen. Wenn du später merkst, dass dein Ressourcen Management Framework doch nicht so toll war oder nicht alles kann, musst du später mehr umbauen, wenn du jetzt irgendwelche Basisklassen erzwingst
    - friend ist auch eine starke Abhängigkeit und es gibt nur ganz selten Fälle, wo man das wirklich braucht. Die mags durchaus geben, aber wenn ich das sehe, ist es erstmal ein gewisses Warnsignal.
    - Wenn es eine Basisklasse gibt, warum Templates? Schaut auch etwas komisch aus. Mag auch sein, dass es einen guten Grund dafür gibt. Aber spontan würde ich denken, entweder ist es ein generisches Framework und der T Parameter reicht dem RecourceManager, um die Teile zu verwalten, oder es basiert auf den Basisklassen und der CResourceManager ist keine Templateklasse, sondern verwaltet Instanzen von der Basisklasse CResource. Warum man beides brauchen sollte, erschließt sich mir erstmal nicht.
    - Warum muss die Resource den Manager kennen? Wieder eine Abhängigkeit, die erstmal unnötig erscheint. Es mag sogar sein, dass sie in deinem Konzept jetzt nötig ist, aber ich könnte mir auch Konzepte vorstellen, bei denen sie das nicht ist. Und wenn du dich erstmal auf unnötige Abhängigkeiten eingelassen hast, macht es dein Programm wieder ein Stück unwartbarer.



  • Interessante Sichtweise.

    Mechanics schrieb:

    - Ableitung ist eine sehr starke Abhängigkeit und sowas ist schlecht und sollte man nach Möglichkeit grundsätzlich vermeiden.

    Vererbung ist erstmal ein Konzept, dass es einem erspart, redundanten Code zu schreiben, und sowas ist grundsätzlich gut.

    Mechanics schrieb:

    - Ich mag es auch nicht, wenn mir irgendwelche Frameworks irgendwelche Basisklassen vorschreiben, von denen ich ableiten muss.

    Kann ich verstehen. Ich programmiere das aber nur für mich, und da ich es selbst geschrieben habe, tut das "Framework" alles genau so, wie ich es will. Somit stellt das keine Bevormundung oder so dar.

    Mechanics schrieb:

    - friend ist auch eine starke Abhängigkeit und es gibt nur ganz selten Fälle, wo man das wirklich braucht. Die mags durchaus geben, aber wenn ich das sehe, ist es erstmal ein gewisses Warnsignal.

    Die Alternative wäre, alles public zu machen.

    Mechanics schrieb:

    - Wenn es eine Basisklasse gibt, warum Templates?

    Weil der Manager auch neue Ressourcen mit new erzeugt. Dafür muss er den Typ kennen.

    Mechanics schrieb:

    - Warum muss die Resource den Manager kennen?

    Die Ressource hat einen ReferenceCounter, d.h. mehrere Objekte werden darauf zugreifen. Sobald ein Objekt die Resource nicht mehr braucht, ruft es CResource::Delete() auf. Dann wird erstmal nur der RefCounter heruntergezählt. Wenn dieser 0 erreicht hat, befiehlt die Resource dem Manager, sie zu löschen. Deswegen der Pointer.



  • Kennst du shared_ptr?



  • Ist das nicht C++11? Mein Compiler (von 2006, ja lacht ruhig 😉 ) kann das noch nicht.



  • Mr Train schrieb:

    Ist das nicht C++11? Mein Compiler (von 2006, ja lacht ruhig 😉 ) kann das noch nicht.

    Ja.
    Und es bietet das, was du am implementieren bist: Ref-counting für Resourcen.



  • @Mr. Train: ja, ich könnte auch selber alle Punkte von mir "widerlegen". Ich wollte dir nur zeigen, was für Überlegungen man hier anstellen könnte (kann gut sein, dass es hier auch andere, besser Punkte gibt, über die man nachdenken sollte).
    Vorsicht, ich würde Vererbung nicht als Konzept sehen, redundanten Code zu sparen. Bzw., das ist auch ein Nebeneffekt der Vererbung, aber ich würds nicht so ausdrücken, weil es eher irreführend ist. Vererbung sollte man nur einsetzen, wenn man das wirklich braucht, sonst eher Komposition. In C++ wird Vererbung auch seltener eingesetzt, als in anderen Sprachen.
    Wenn du die "Ressourcen" auch an anderen Stellen wirklich allgemein als Ressourcen betrachtest, ohne dich konkret dafür zu interessieren, was für Ressourcen das konkret sind, dann mach deine Basisklasse. Aber wenn du sie nur wegen dem Ressourcenmanager brauchst, würd ich das eher nicht machen.

    Für ältere Compiler gibts boost::shared_ptr, verwende ich auch. Selbst wenn du aus irgendeinem Grund kein boost verwenden willst, könntest du selber so eine Art Container schreiben. Der Punkt ist, so ein Container/Smart Pointer ist etwas externes, deine Klasse ist in keinster Weise davon abhängig und muss es nicht kennen.

    Dass du es nur für dich selbst programmierst, ist erstmal kein Argument. Ich weiß nicht, warum du das alles machst, aber ich nehme erstmal an, dass du das lernen und später in der Branche arbeiten willst, oder vielleicht arbeitest du ja sogar schon in der Branche. Und da wirst du wahrscheinlich auch irgedwann so weit kommen, dass du an großen Projekten mit vielen Entwicklern arbeitest. Und dann fallen Punkte wie Wartbarkeit viel stärker ins Gewicht.

    Nein, friend ist kein Alternative zu public. Es geht immer auch ganz anders.



  • Mechanics schrieb:

    Nein, friend ist kein Alternative zu public. Es geht immer auch ganz anders.

    friend ist nichts schlechtes. Friend koppelt zwar 2 Klassen sehr eng miteinander, aber zumeist wären es ohne nicht 2 Klassen, sondern nur eine Große. Viele Lösungen mit friend sind schöner als ganz anders.



  • otze schrieb:

    aber zumeist wären es ohne nicht 2 Klassen, sondern nur eine Große.

    Oder zwei Klassen, von denen mindestens eine Dinge protected oder gar public hat, die besser private sein sollten.


Anmelden zum Antworten