Softwarearchitektur Initialisierung Grafik API mit Handle





  • Was die Sportfreunde mit Interface-Klasse meinten ist folgendes:

    class APIInterface
    {
    public:
         virtual const std::string &getName() const = 0;
         virtual void doStuff() = 0;
    }
    
    class DirectXAPI : public APIInterface
    {
    public:
        virtual const std::string &getName() const override { return "DirectXAPI"; }
        virtual void doStuff() override { /* do DirectX-Shit */ }
    }
    
    class VulkanAPI : public APIInterface
    {
    public:
        virtual const std::string &getName() const override { return "VulkanAPI"; }
        virtual void doStuff() override { /* do Vulkan-Shit */ }
    }
    

    D.h. es gibt eine BasisKlasse, die alle benötigten Funktionen anbietet, aber nicht implementiert.
    Und darüberhinaus gibt es Klassen, die von dieser Basisklasse ableiten und dann die eigentliche API-spezifische Funktionalität hinterlegen. Eigentlich perfekt für deinen Anwendungsfall, WENN die beiden APIs überhaupt vereinheitlicht werden können.

    Was genau du mit dem Handle tun willst, ist mir nicht klar. Mir scheint aber, das wäre ein guter Anwendungsfall für eine Art Singleton bzw. für ein statisches Objekt, welches vom Typ der Basisklasse ist ( Interface ) und wo im Grunde für den Anwender unerheblich ist, was wirklich dahintersteckt. Der Nutzer arbeitet dann nur mit diesem Singleton.

    Ich hoffe ich habe verstanden worauf du hinaus willst 😉



  • @It0101 und @DocShoe
    ich will nun die Architektur von @DocShoe nutzen, da werden Interfaces benutzt.
    Das ähnelt ja auch soweit deinen Vorschlag @It0101, wenn ich das richtig sehe.
    Ich merke, in dem Bereich Polymorphie und Software Architektur hab ich noch so leicht meine Schwächen.
    So wie ich das verstehe, scheint es sich dabei um ein Strategie Pattern in Kombination eines Factory Patterns zu handeln.

    @It0101
    Das Singleton ist eins der wenigen Design Pattertns, die ich bereits kenne.
    Jedoch hab ich da mal gelesen, es wäre ein Anti Pattern. Seit dem hab ich das Design Pattern nicht mehr verwendet.
    Andererseite würde es für einen Renderer ja Sinn machen, aber wie gesagt bin mir da total unsicher, weil viele sagen man solls nicht verwenden, während andere sagen, man kann es verwenden.

    Jetzt bin ich an dem Punkt, dass ich ein Problem mit Shared_Ptr hab. So wie ich das verstehe, können Shared_Ptr an eine andere Instanz übergeben werden, während Unique_Ptr nicht an eine andere Instanz übergeben werden können.
    Jedoch bereiten mir noch zwei Fehlermeldungen Probleme.
    'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>' und
    'return': cannot convert from 'std::shared_ptr<DirectXRenderer>' to 'std::shared_ptr<IRenderer>'. Ich verstehe nicht so ganz wieso das nicht funktioniert, weil beide Klassen VulkanRenderer und DirectXRenderer sind doch Kindklassen von IRenderer.

    class IRenderer
    {
    	// erzeugende Engine
    	IGraphicsEngine* Engine = nullptr;
    
    public:
    	IRenderer() = delete;
    	IRenderer(IGraphicsEngine* engine) :
    		Engine(engine)
    	{
    	}
    	virtual ~IRenderer() = default;
    
    	IGraphicsEngine* engine()
    	{
    		return Engine;
    	}
    
    	// Engine-spezifische Implementation
    	virtual void render() = 0;
    };
    
    class DirectXEngine : public IGraphicsEngine
    {
    public:
    	DirectXEngine() :
    		IGraphicsEngine(EngineType::DirectX, "DirectX", "12.1")
    	{
    
    	}
    
    	std::shared_ptr<IRenderer> create_renderer() const override
    	{
    		return std::make_shared<DirectXRenderer>(this);
    	}
    };
    
    class VulkanEngine : public IGraphicsEngine
    {
    public:
    	VulkanEngine() :
    		IGraphicsEngine(EngineType::Vulkan, "Vulkan", "1.2.137")
    	{
    		
    	}
    
    	std::shared_ptr <IRenderer> create_renderer() const override
    	{
    		return std::make_shared<VulkanRenderer >(this);
    	}
    };
    

    ich merke nun, dass ich mich noch einmal in die Themen Polymorphie und Smart Pointers einlesen sollte.
    Das sind wohl bei der Nutzung von Design Pattern standard Techniken.



  • @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>' und
    'return': cannot convert from 'std::shared_ptr<DirectXRenderer>' to 'std::shared_ptr<IRenderer>'. Ich verstehe nicht so ganz wieso das nicht funktioniert, weil beide Klassen VulkanRenderer und DirectXRenderer sind doch Kindklassen von

    Prüfe mal deine Klassendefinition von Vulkanrenderer. Theoretisch sollte irgendwo stehen:

    class VulkanRenderer : public IRenderer
    {
    }
    


  • @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    class DirectXEngine : public IGraphicsEngine

    Ich verstehe nicht so ganz wieso das nicht funktioniert, weil beide Klassen VulkanRenderer und DirectXRenderer sind doch Kindklassen von IRenderer.

    Offenbar nicht, DirectXEngine erbt von IGraphicsEngine und nicht von IRenderer

    Edit: zu langsam



  • @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    std::shared_ptr<IRenderer> create_renderer() const override

    Ich würde einen Fehler durch das const erwarten.

    Du zeigst keine vollständigen Fehlermeldungen!



  • @manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    std::shared_ptr<IRenderer> create_renderer() const override

    Ich würde einen Fehler durch das const erwarten.

    Du zeigst keine vollständigen Fehlermeldungen!

    Warum sollte das const ein Problem sein?

    Das "const" sagt ja nur: Diese Funktion verändert nicht das Klassen-Objekt selbst. Das tut die Funktion auch nicht.



  • @It0101 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    std::shared_ptr<IRenderer> create_renderer() const override

    Ich würde einen Fehler durch das const erwarten.

    Du zeigst keine vollständigen Fehlermeldungen!

    Warum sollte das const ein Problem sein?

    Das "const" sagt ja nur: Diese Funktion verändert nicht das Klassen-Objekt selbst. Das tut die Funktion auch nicht.

    Was erwartet IRenderer im Konstruktor?



  • @manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @It0101 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    std::shared_ptr<IRenderer> create_renderer() const override

    Ich würde einen Fehler durch das const erwarten.

    Du zeigst keine vollständigen Fehlermeldungen!

    Warum sollte das const ein Problem sein?

    Das "const" sagt ja nur: Diese Funktion verändert nicht das Klassen-Objekt selbst. Das tut die Funktion auch nicht.

    Was erwartet IRenderer im Konstruktor?

    Er übernimmt eine Kopie einer "Zahl" ( den this-Pointer) um es mal ganz plump zu sagen. Und weiter?



  • @It0101 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    Er übernimmt eine Kopie einer "Zahl" um es mal ganz plump zu sagen

    Plump und falsch.



  • @DocShoe sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    Ist so runtergeschrieben, also kein Anspruch auf Übersetzbarkeit.

    Duckundwech...



  • @manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @It0101 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    Er übernimmt eine Kopie einer "Zahl" um es mal ganz plump zu sagen

    Plump und falsch.

    VulkanRenderer ( VulkanEngine* engine )
    

    ok, in dem Fall hast du Recht. Der Pointer wird nicht als "const" übernommen. Nehme alles zurück und behaupte das Gegenteil 😉



  • oO, was hab ich hier ausgelöst 😅
    Da war ich eine Stunde weg und es entsteht direkt eine Diskussion 😃
    Also das mit den Klassen scheint richtig zu sein.
    Die Renderer Klassen erben von IRenderer und und die Graphics erben von IGraphicsEngine.

    @manni66
    Was meinst du damit, dass ich keine vollständigen Fehlermeldungen zeige.
    Gibt es noch eine Möglichkeit detailliertere Meldungen zu bekommen?
    Ich hab die aus der Visual Studio 'Error List' kopiert.

    @manni66 und @It0101
    Ich habs mal ohne const probiert. Aber dies funktioniet auch nicht.
    Daran scheint es nicht zu liegen 😞
    Die Zeile return std::make_shared<VulkanRenderer >(this); müsste ja ein Objekt von sich selbst (VulkanEngine) übergeben und genau das erwartet:

    VulkanRenderer(VulkanEngine* engine) :
    		IRenderer(engine)
    	{
    	}
    

    Deswegen verstehe ich nicht, warum der die Übergabe nicht akzeptiert. Daran liegt es ja vermute ich?

    Btw: Ich hab mal den Code angehängt. Der ähnelt im weitesten den Snippet von @DocShoe

    #include <memory>
    #include <string>
    #include <memory>
    #include <string>
    #include <iostream>
    
    namespace EngineType
    {
    	enum Type
    	{
    		Unknown,
    		DirectX,
    		Vulkan
    	};
    }
    
    class VulkanRenderer;
    class DirectXRenderer;
    class IRenderer;
    
    class IGraphicsEngine
    {
    	EngineType::Type Type_ = EngineType::Unknown;
    	std::string const Name_;
    	std::string const Version_;
    
    public:
    	IGraphicsEngine(EngineType::Type type, std::string const& name, std::string const& version) :
    		Type_(type),
    		Name_(name),
    		Version_(version)
    	{
    	}
    
    	EngineType::Type type() const
    	{
    		return Type_;
    	}
    
    	std::string const& name() const
    	{
    		return Name_;
    	}
    
    	std::string const& version() const
    	{
    		return Version_;
    	}
    
    	virtual std::shared_ptr<IRenderer> create_renderer() const = 0;
    };
    
    // abstrakte Basisklasse für einen Renderer
    class IRenderer
    {
    	// erzeugende Engine
    	IGraphicsEngine* Engine = nullptr;
    
    public:
    	IRenderer() = delete;
    	IRenderer(IGraphicsEngine* engine) :
    		Engine(engine)
    	{
    	}
    	virtual ~IRenderer() = default;
    
    	IGraphicsEngine* engine()
    	{
    		return Engine;
    	}
    
    	// Engine-spezifische Implementation
    	virtual void render() = 0;
    };
    
    class DirectXEngine : public IGraphicsEngine
    {
    public:
    	DirectXEngine() :
    		IGraphicsEngine(EngineType::DirectX, "DirectX", "12.1")
    	{
    
    	}
    
    	std::shared_ptr<IRenderer> create_renderer() const override
    	{
    		return std::make_shared<DirectXRenderer>(this);
    	}
    };
    
    class DirectXRenderer : public IRenderer
    {
    public:
    	// möglicherweise braucht der Renderer noch Engine-spezifische Objekte, um eingesetzt werden
    	// zu können. Die könnten dann als zusätzliche Parameter im Konstruktor übergeben werden.
    	DirectXRenderer(DirectXEngine* engine) :
    		IRenderer(engine)
    	{
    	}
    
    	void render() override
    	{
    		// tu was DirectX-spezifisches
    	}
    };
    
    class VulkanEngine : public IGraphicsEngine
    {
    public:
    	VulkanEngine() :
    		IGraphicsEngine(EngineType::Vulkan, "Vulkan", "1.2.137")
    	{
    		
    	}
    
    	std::shared_ptr <IRenderer> create_renderer() const override
    	{
    		return std::make_shared<VulkanRenderer >(this);
    	}
    };
    
    class VulkanRenderer : public IRenderer
    {
    public:
    	// möglicherweise braucht der Renderer noch Engine-spezifische Objekte, um eingesetzt werden
    	// zu können. Die könnten dann als zusätzliche Parameter im Konstruktor übergeben werden.
    	VulkanRenderer(VulkanEngine* engine) :
    		IRenderer(engine)
    	{
    	}
    
    	void render() override
    	{
    		// tu was Vulkan-spezifisches
    	}
    };
    
    std::shared_ptr<IGraphicsEngine> create_engine(EngineType::Type type)
    {
    	if (type == EngineType::DirectX) return std::make_shared<DirectXEngine>();
    	//else if (type == EngineType::Vulkan) return std::make_shared<VulkanEngine>();
    	return std::shared_ptr<IGraphicsEngine>();
    }
    
    
    int main()
    {
    	std::shared_ptr<IGraphicsEngine> engine = create_engine(EngineType::DirectX);
    	std::cout << "Verwendete Engine: " << engine->name() << std::endl;
    	return 0;
    }
    


  • Was manni angesprochen hat ist Folgendes:

    std::shared_ptr<IRenderer> create_renderer() const override
    {
       return std::make_shared<DirectXRenderer>(this); // <- dieses this ist const, da die member Funktion const ist
    }
    

    und

    IRenderer(IGraphicsEngine* engine) : // <- der Parameter ist nicht const
       Engine(engine)
    {
    }
    

    Da sollte der Compiler einen Fehler melden.
    Wenn du jetzt schon dabei bist das Ganze ordentlich zu machen, dann teil das bitte in mehrere Dateien und trenne klar zwischen .h und .cpp Datei. Hier im Forum habe ich das nur so runtergeschrieben, aber in einem echten Projekt würde ich das in mehrere Dateien aufteilen.



  • @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    Gibt es noch eine Möglichkeit detailliertere Meldungen zu bekommen?
    Ich hab die aus der Visual Studio 'Error List' kopiert.

    Output Tab.

    @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    müsste ja ein Objekt von sich selbst (VulkanEngine)

    Ein const Objekt.

    @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    Btw: Ich hab mal den Code angehängt.

    Wenn das der richtige Code ist: Reihenfolge! make_schared sieht nur die Vorwärtsdeklaration.



  • Ah ok danke.

    @manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    Gibt es noch eine Möglichkeit detailliertere Meldungen zu bekommen?
    Ich hab die aus der Visual Studio 'Error List' kopiert.

    Output Tab.

    Im Output Tab finde ich folgendes:
    c:...\testbed.cpp(87): error C2440: 'return': cannot convert from 'std::shared_ptr<DirectXRenderer>' to 'std::shared_ptr<IRenderer>'
    1>c:...\testbed.cpp(87): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    1>c:...\testbed.cpp(118): error C2440: 'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>'
    1>c:...\testbed.cpp(118): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

    @manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    müsste ja ein Objekt von sich selbst (VulkanEngine)

    Ein const Objekt.

    Hmm, ok. Aber daran scheint es nicht zu liegen.
    Das Problem tritt auch auf, wenn ich const weg lasse.

    @manni66 sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @Pixma sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    Btw: Ich hab mal den Code angehängt.

    Wenn das der richtige Code ist: Reihenfolge! make_schared sieht nur die Vorwärtsdeklaration.

    Ich hab doch die benötigten Klassen vordeklariert?

    #include <memory>
    #include <string>
    #include <memory>
    #include <string>
    #include <iostream>
    
    namespace EngineType
    {
    	enum Type
    	{
    		Unknown,
    		DirectX,
    		Vulkan
    	};
    }
    
    // Vorwärtsdeklaration
    class VulkanRenderer;
    class DirectXRenderer;
    class IRenderer;
    


  • Eben. Nur vorwärts deklariert. Du sagst dem Compiler damit, dass es irgendwo eine Klasse mit dem Namen gibt, aber mehr auch nicht. Zu dem Zeitpunkt kennt er keine Details dieser Klasse, er weiß also nicht, dass sie von IRenderer erbt. Er kann auch keine Instanz erzeugen, da er nicht weiß, wie das Layout der Klasse aussieht.



  • @DocShoe sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    Eben. Nur vorwärts deklariert. Du sagst dem Compiler damit, dass es irgendwo eine Klasse mit dem Namen gibt, aber mehr auch nicht. Zu dem Zeitpunkt kennt er keine Details dieser Klasse, er weiß also nicht, dass sie von IRenderer erbt. Er kann auch keine Instanz erzeugen, da er nicht weiß, wie das Layout der Klasse aussieht.

    Irgendwie bin ich grad echt verwirrt.
    Ich hab mal nach forward declaration in Kombination mit Shared Pointers mit Google gesucht.
    Gefunden hab ich jedoch nur das: https://stackoverflow.com/questions/16588075/forward-declarations-and-shared-ptr
    Und das macht irgendwie keinen Sinn und funktioniert bei mir auch nicht.



  • #include <memory>
    
    class foo;  // forward declaration
    
    using foo_ptr = std::shared_ptr<foo>;
    
    class foo {};  // definition
    
    int main()
    {
    	foo_ptr p{ std::make_shared<foo>() };  // definition muss bekannt sein
    }
    


  • @Swordfish sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    #include <memory>
    
    class foo;  // forward declaration
    
    using foo_ptr = std::shared_ptr<foo>;
    
    class foo {};  // definition
    
    int main()
    {
    	foo_ptr p{ std::make_shared<foo>() };  // definition muss bekannt sein
    }
    

    Das funktioniert bei mir nicht. Die gleichen Fehler kommen immer noch.
    Ich hab mal im Internet geschaut nochmal nach shared_ptr in Verbindung mit Vorwärtsdeklarationen. Die meisten beispiele benutzen typedef wie in dem Stackoverflow post.

    class IGraphicsEngine;
    class IRenderer;
    using irenderer_ptr = std::shared_ptr< IRenderer>;
    
    class VulkanRenderer;
    using vulkanRenderer_ptr = std::shared_ptr< VulkanRenderer>;
    
    class DirectXRenderer;
    using directxRenderer_ptr = std::shared_ptr< DirectXRenderer>;
    
    ...
    
    class DirectXEngine : public IGraphicsEngine
    {
    public:
    	DirectXEngine() :
    		IGraphicsEngine(EngineType::DirectX, "DirectX", "12.1")
    	{
    
    	}
    
    	std::shared_ptr<IRenderer> create_renderer() const override
    	{
    		return  directxRenderer_ptr{ std::make_shared<DirectXRenderer>(this) };
    	}
    };
    
    ...
    class VulkanEngine : public IGraphicsEngine
    {
    public:
    	VulkanEngine() :
    		IGraphicsEngine(EngineType::Vulkan, "Vulkan", "1.2.137")
    	{
    
    	}
    
    	std::shared_ptr <IRenderer> create_renderer() const override
    	{
    		return vulkanRenderer_ptr{ std::make_shared<VulkanRenderer >(this) };
    	}
    };
    
    

    Wenn ich statt return directxRenderer_ptr { std::make_shared<DirectXRenderer>(this) }; folgende Zeile nutze: return directxRenderer_ptr p{ std::make_shared<DirectXRenderer>(this) }; bekomme ich folgende Fehlermeldungen:
    1>c:...\testbed.cpp(93): error C2146: syntax error: missing ';' before identifier 'p'
    1>c:...\testbed.cpp(93): error C2275: 'directxRenderer_ptr': illegal use of this type as an expression
    1>c:...\testbed.cpp(25): note: see declaration of 'directxRenderer_ptr'
    1>c:...\testbed.cpp(93): error C2065: 'p': undeclared identifier

    Folgende Variante hab ich auch nochmal ausprobiert:

    	std::shared_ptr<IRenderer> create_renderer() const override
    	{
    		directxRenderer_ptr p{ std::make_shared<DirectXRenderer>(this) };
    		return p;
    	}
    

    Diese Funktioniert jedoch auch nicht.
    Da bekomme ich wieder die typischen Fehlermeldungen, welche ich vorher hatte.
    1>c:...\testbed.cpp(94): error C2440: 'return': cannot convert from 'directxRenderer_ptr' to 'std::shared_ptr<IRenderer>'
    1>c:...\testbed.cpp(94): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    1>c:...\testbed.cpp(125): error C2440: 'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>'
    1>c:...\testbed.cpp(125): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called


Anmelden zum Antworten