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.



  • @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



  • Wo ist denn deine Klassendefinition von VulkanRenderer und DirectXRenderer? Diese müssen der Klassenfunktion create_renderer() bekannt sein.
    So wie @DocShoe schon geschrieben hat, lagere den Code am besten in Header- und Sourcedateien aus (und die Funktionsdefinitionen kommen dann in die Sourcedateien und vorher alles nötige per #include "..."einbinden).



  • die waren noch weiter unten.
    Ich habe die beiden Klassen nun mal weiter hoch geschoben.
    Nun meckert er da nicht mehr, jedoch bekomme ich nun andere Fehlermeldungen.

    1>c:...\testbed.cpp(90): error C2664: 'IRenderer::IRenderer(const IRenderer &)': cannot convert argument 1 from 'DirectXEngine *' to 'IGraphicsEngine *'
    1>c:...\testbed.cpp(91): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
    1>c:...\testbed.cpp(124): error C2664: 'IRenderer::IRenderer(const IRenderer &)': cannot convert argument 1 from 'VulkanEngine *' to 'IGraphicsEngine *'
    1>c:...\testbed.cpp(125): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

    Diese verstehe ich auch nicht so richtig, weil VulkanEngine und DirectXEngine ja IGraphicEngine Objekte sind.
    Irgendwie hab ich das Gefühll, ich drehe mich dabei im Kreis.

    Ich hab zur Überischtlichkeitshalber nochmal mein Code angehängt:

    #include <memory>
    #include <string>
    #include <memory>
    #include <string>
    #include <iostream>
    
    namespace EngineType
    {
    	enum Type
    	{
    		Unknown,
    		DirectX,
    		Vulkan
    	};
    }
    
    class IGraphicsEngine;
    class IRenderer;
    
    class DirectXEngine;
    class VulkanEngine;
    
    class VulkanRenderer;
    using vulkanRenderer_ptr = std::shared_ptr< VulkanRenderer>;
    
    class DirectXRenderer;
    using directxRenderer_ptr = std::shared_ptr< DirectXRenderer>;
    
    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 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 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 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
    	}
    };
    
    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) };
    	}
    };
    
    
    
    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;
    }
    

    Ich denke ich werde, das ganze mal nachher in einzelne cpp und h files aufteilen. Ich denke dann löst sich das Problem von selbst.



  • Ja, du hast zirkuläre Abhängigkeiten, da sich jeweils beide Engine und Renderer-Klassen kennen müssen. Und das kannst du nur per Auslagerung lösen.

    Jedoch finde ich das mit der direkten Abhängigkeit vom Renderer auf die Engine nicht gut, also das hier

    VulkanRenderer(VulkanEngine* engine)
    

    Besser wäre es dann ein VulkanSettings o.ä. (bzw. ISettings) als Parameter zu übergeben.

    Zum Test (daß es auch ohne Auslagerung) kompiliert, laß einfach mal diesen Parameter sowohl im jeweiligen Konstruktor als auch beim Aufruf weg (bzw. auskommentieren).



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

    Hmm ok, ich hatte vor das so zu entwickeln, dass der Benutzer eine Art Config Objekt von Vulkan zurückgegeben bekommt. Jedoch sollte dieses Objekt dem Benutzer verborgen bleiben. Das Config Objekt sollte, dann den Pipeline, CommandBuffer Klassen übergeben werden, da diese alle eine Instanz benötigen.
    Mein Grundziel war es in meiner Grafikengine zwischen DirectX und Vulkan beim Enginestart auszuwählen.

    1. Wieso willst du das alles auf dem Config-Objekt aufhängen? Das Config-Objekt sollte in einfachen Fällen genau 1x benötigt werden, und zwar wenn du das Engine-Objekt erzeugst. Dabei entsteht dann entweder die DX-Engine oder die Vulkan-Engine. Die wissen dann eh was sie sind und alles ist gut. Und das Engine-Objekt musst du dann auch nicht mehr verstecken.
      Wenn du es unbedingt gegen "direkte" Verwendung schützen willst, das geht dann auch anders. Nur das macht meist keinen Sinn. Wenn der Benutzer sich in den Fuss schiessen will, dann schafft er das immer irgendwie. Wichtig ist es bloss es ihm schwer zu machen das unabsichtlich zu tun aber es ihm leicht zu machen das Ding ohne gross darüber nachzudenken richtig zu verwenden.

    Du hast das mit delete und den Destruktoren nicht verstanden (dx->~InvisionRenderer(); - WTF?).
    Ich weiß, dass ist totaler Käse und das macht man nicht. Jedoch hatte ich da das Problem, dass am Ende der Destruktor in meinen Programm nicht aufgerufen wurde.
    Aber ja, die Zeile gehört gelöscht.

    De gehört ein delete dx hin. Das ruft den Desturktor auf und gibt dann den Speicher wieder frei. Das was du geschrieben hast ruft nur den Desturktor auf aber der Speicher bleibt belegt - und das ist ein Memory-Leak.

    Du hast das Prinzip der Kapselung nicht verstanden.
    Ach, du meinst den direkten Zugriff? Also nicht über Getter und Setter, wenn ich das richtig verstehe.
    Sorry, ich hatte vergessen zu erwähnen, dass es sich dabei um einen Prototypen handelt. Ich wollte erst einmal schauen ob die Architektur was taugt.

    Nein, ich meine nicht lauter sinnlose Getter und Setter zu schreiben. Das wäre Fingerakrobatig aber wenn du Getter und Setter für alles anbietest erhöht das überhaupt nicht die Kapselung. Ich meine dass überhaupt von aussen auf Eingeweide einer Klasse zugegriffen wird. Das ist in deinem Fall nur nötig weil du ein vollkommen verqueres Design gebastelt hast.

    Was genau versprichst du dir von deinem sog. HANDLE?
    Ich wollte das config Objekt verbergen. Dieses Objekt soll als eine Art Instanz dienen.

    Siehe oben, mMn. einfach nicht nötig. Kann in bestimmten Fällen Sinn machen, aber hauptsächlich wenn man den Code einer Library nicht mit ausliefert. Oder wenn man "echte" Handles bastelt, d.h. nicht einfach einen Zeiger castet sondern über z.B. einen Handle-Table geht.

    throw "undefined Handle"; - don't make baby Jesus cry.
    Das ist auch provisorisch, wird natürlich noch durch eine passende Exception ersetzt.

    OK. Tip für's nächste mal: schreib statt dessen einfach sowas wie throw SomeNiceException();. Dann versteht jeder dass es nicht final ist und du bekommst keine Kommenare dass String-Pointer werfen pfui ist.

    Beim beantworten der Fragen ist mir auch aufgefallen, dass ich vergessen hatte zu erwähnen, dass es sich hierbei aktuell noch um einen Prototypen handelt.

    Das war schon klar. Nur können wir nicht wissen was genau du in deinem Prototypen als schnellen Hack ansiehst der noch geändert werden muss und was nicht. Im Zweifelsfall reicht auch wenn du sowas wie

    throw "undefined Handle"; // TODO: passende Exception-Klasse schreiben und verwenden
    

    schreibst. Dann versteht man das auch ohne lange Erklärung.



  • ich muss mir da nochmal Gedanken machen, weil auch wenn ich die Parameter entferne und den entfernten Standardkonstruktor wieder einfüge kommt es zu Fehlermeldungen. Ich vermute, dass ich mir da nochmal Generell Gedanken machen muss.
    Aktuell hab ich die Idee, dass ich eine Engine Klasse habe und diese wird initialisiert über ein Settings Interface wie du beschrieben hast das Objekt passend für Vulkan oder DirectX existiert. Im nächsten Schritt dient die Engine Klasse mir dann als Factory um mir die passenden Objekte für Renderer, CommandBuffer, Pipelines usw zu erzeugen.

    So stelle ich mir das in der Art vor:

    class ISettings
    {
       virtual void name() = 0;
       virtual void version() = 0;
       ...
    }
    
    class VulkanSettings : public ISettings
    {
       void name() override
       {
    
       }
    
      void version() override
      {
    
      }
    
      void VulkanSpezifischeFunktion()
      {
    
      }
    }
    
    class DirectXSettings : public ISettings
    {
      ....
    }
    
    class VulkanEngine : public IGraphicsEngine
    {
    public:
    	VulkanEngine() :
    		IGraphicsEngine(EngineType::Vulkan, ISettings settings)
    	{
    		
    	}
    
    	std::shared_ptr <IRenderer> create_renderer() const override
    	{
    		return std::make_shared<VulkanRenderer >(this);
    	}
    };
    
    class DirectXEngine : public IGraphicsEngine
    {
    public:
    	DirectXEngine() :
    		IGraphicsEngine(EngineType::DirectX, ISettings settings)
    	{
    
    	}
    
    	std::shared_ptr<IRenderer> create_renderer() const override
    	{
    		return std::make_shared<DirectXRenderer>(this);
    	}
    };
    
    

    Wobei mir das immer noch nicht so richtig gefällt, weil dann theoretisch folgende Übergabe möglich ist:
    IGraphicsEngine(EngineType::Vulkan, DirectXSettings) und das wäre falsch.

    @hustbaer
    danke für deine Tipps 🙂
    Ich werde nun new und delete vermeiden wenn möglich und mehr auf Smart Pointer setzen.
    Sonst ist die Gefahr einfach zu groß, dass Memory Leaks sich einschleichen.
    Ich denke durch die aktuelle Architekturidee könnte das mit der Kapseselung besser werden.
    So wenn ich deine Erklärung der Handles richtig verstanden hab, machen Handles dann mehr Sinn für verschiedene Objekte die im Speicher verwaltet werden.
    Jedes Objekt zum Beispiel ein Mesh oder eine Textur bekommt, dann ein Handle zugewiesen. Verwaltet wird das ganze dann über eine Handle Tabelle, wo jedes Objekt einem Handle zugeordnet werden kann.
    Den Tipp mit dem throw werde ich mir merken und das nächste mal anwenden 🙂
    Ich merke, dass ich denke generell öfters mal Kommentare anwenden muss.



  • Ich habe mich nun noch einmal dran gesetzt und den Code aufgeteilt in mehrere cpp und h Dateien.
    Jedoch bekomme ich immernoch die gleichen Fehler. Ich weiß langsam echt nicht mehr weiter.
    Des Weiteren ist mir aufgefallen, dass das Programm läuft sobald ich die Zeile virtual std::shared_ptr<IRenderer> create_renderer() const = 0; (IGraphicsEngine, Zeile 32) und der overwrites auskommentiere. Ich habe die Stellen mit einem Kommentar '// Problemfall' gekennzeichnet. Bei den Fehlermeldungen werden einige Fehler mit Bezug auf die Memory File angezeigt, ich vermute die hängen mit den bekannten Fehlermeldungen zusammen.

    Fehlermeldungen [Error List]

    Severity	Code	Description	Project	File	Line	Suppression State
    Error	C2664	'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'	Testbed	c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory	1801	
    Error	C2440	'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>'	Testbed	c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h	15	
    Error	C2440	'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>'	Testbed	c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h	15	
    Error	C2440	'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>'	Testbed	c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h	15	
    Error	C2440	'return': cannot convert from 'std::shared_ptr<DirectXRenderer>' to 'std::shared_ptr<IRenderer>'	Testbed	c:\users\dennis\source\repos\testbed\testbed\directxengine.h	15	
    Error	C2664	'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'	Testbed	c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory	1801	
    
    

    Fehlermeldungen [Output]

    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1801): error C2664: 'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'
    1>        with
    1>        [
    1>            _Ty=const DirectXEngine *
    1>        ]
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1799): note: Conversion loses qualifiers
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1865): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>        with
    1>        [
    1>            _Ty=DirectXRenderer
    1>        ]
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1866): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>        with
    1>        [
    1>            _Ty=DirectXRenderer
    1>        ]
    1>c:\users\dennis\source\repos\testbed\testbed\directxengine.h(15): note: see reference to function template instantiation 'std::shared_ptr<DirectXRenderer> std::make_shared<DirectXRenderer,const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>DirectXRenderer.cpp
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1801): error C2664: 'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'
    1>        with
    1>        [
    1>            _Ty=const DirectXEngine *
    1>        ]
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1799): note: Conversion loses qualifiers
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1865): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>        with
    1>        [
    1>            _Ty=DirectXRenderer
    1>        ]
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1866): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>        with
    1>        [
    1>            _Ty=DirectXRenderer
    1>        ]
    1>c:\users\dennis\source\repos\testbed\testbed\directxengine.h(15): note: see reference to function template instantiation 'std::shared_ptr<DirectXRenderer> std::make_shared<DirectXRenderer,const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>Testbed.cpp
    1>c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h(15): error C2440: 'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>'
    1>c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h(15): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    1>c:\users\dennis\source\repos\testbed\testbed\directxengine.h(15): error C2440: 'return': cannot convert from 'std::shared_ptr<DirectXRenderer>' to 'std::shared_ptr<IRenderer>'
    1>c:\users\dennis\source\repos\testbed\testbed\directxengine.h(15): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    1>VulkanEngine.cpp
    1>c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h(15): error C2440: 'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>'
    1>c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h(15): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    1>VulkanRenderer.cpp
    1>c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h(15): error C2440: 'return': cannot convert from 'std::shared_ptr<VulkanRenderer>' to 'std::shared_ptr<IRenderer>'
    1>c:\users\dennis\source\repos\testbed\testbed\vulkanengine.h(15): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    

    DirectXEngine.cpp

    
    
    #include "IRenderer.h"
    #include "DirectXRenderer.h"
    #include "DirectXEngine.h"
    
    DirectXEngine::DirectXEngine() :
    	IGraphicsEngine(EngineType::DirectX, "DirectX", "12.1")
    {
    
    }
    
    std::shared_ptr<IRenderer> DirectXEngine::create_renderer()
    {
    	return directxrenderer_ptr{ std::make_shared<DirectXRenderer>(this) };
    
    }
    

    DirectXEngine.h

    #pragma once
    #include <memory>
    #include "IGraphicsEngine.h"
    
    class DirectXRenderer;
    using directxrenderer_ptr = std::shared_ptr<DirectXRenderer>;
    
    class DirectXEngine : public IGraphicsEngine
    {
    public:
    	DirectXEngine();
    
    	std::shared_ptr<IRenderer> create_renderer() override;
    };
    

    DirectXRenderer

    #include "DirectXRenderer.h"
    #include "DirectXEngine.h"
    
    DirectXRenderer::DirectXRenderer(DirectXEngine* engine) :
    	IRenderer(engine)
    {
    }
    
    void DirectXRenderer::render()
    {
    	// tu was DirectX-spezifisches
    }
    

    DirectXRenderer.h

    #pragma once
    #include <memory>
    #include "IRenderer.h"
    
    
    class DirectXEngine;
    
    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);
    
    	void render() override;
    };
    

    IGraphicsEngine.cpp

    #include "IGraphicsEngine.h"
    
    
    IGraphicsEngine::IGraphicsEngine(EngineType::Type type, std::string const& name, std::string const& version) :
    	Type_(type),
    	Name_(name),
    	Version_(version)
    {
    }
    
    EngineType::Type IGraphicsEngine::type() const
    {
    	return Type_;
    }
    
    std::string const& IGraphicsEngine::name() const
    {
    	return Name_;
    }
    
    std::string const& IGraphicsEngine::version() const
    {
    	return Version_;
    }
    

    IGraphicsEngine.h

    #pragma once
    #include <memory>
    #include <string>
    #include "IRenderer.h"
    
    namespace EngineType
    {
    	enum Type
    	{
    		Unknown,
    		DirectX,
    		Vulkan
    	};
    }
    
    
    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);
    
    	EngineType::Type type() const;
    
    	std::string const& name() const;
    
    	std::string const& version() const;
    
           // Problemfall
    	virtual std::shared_ptr<IRenderer> create_renderer() = 0;
    };
    

    IRenderer.cpp

    #include "IRenderer.h"
    
    #include "IGraphicsEngine.h"
    

    IRenderer.h

    #pragma once
    
    class IGraphicsEngine;
    
    // 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;
    };
    

    Testbed.cpp

    #include <iostream>
    
    #include "VulkanEngine.h"
    #include "DirectXEngine.h"
    
    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;
    }
    

    VulkanEngine.cpp

    #include "VulkanEngine.h"
    
    
    #include "IRenderer.h"
    #include "VulkanRenderer.h"
    
    VulkanEngine::VulkanEngine() :
    	IGraphicsEngine(EngineType::Vulkan, "Vulkan", "1.2.137")
    {
    
    }
    
    std::shared_ptr <IRenderer> VulkanEngine::create_renderer()
    {
    	return vulkanrenderer_ptr{ std::make_shared<VulkanRenderer >(this) };
    }
    

    VulkanEngine.h

    
    #pragma once
    #include <memory>
    #include "IGraphicsEngine.h"
    
    class VulkanRenderer;
    using vulkanrenderer_ptr = std::shared_ptr<VulkanRenderer>;
    
    class VulkanEngine : public IGraphicsEngine
    {
    public:
    	VulkanEngine();
    
    	std::shared_ptr <IRenderer> create_renderer() override;
    };
    
    

    VulkanRenderer.cpp

    
    #include "VulkanEngine.h"
    
    #include "VulkanRenderer.h"
    
    
    VulkanRenderer::VulkanRenderer(VulkanEngine* engine) :
    	IRenderer(engine)
    {
    }
    
    void VulkanRenderer:: render()
    {
    	// tu was Vulkan-spezifisches
    }
    

    VulkanRenderer.h

    #pragma once
    
    #include "IRenderer.h"
    
    class VulkanEngine;
    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);
    
    	void render() override;
    };
    


  • @Pixma Ausgerechnet die problematischen Funktionen bleiben im Header?



  • @Pixma und deine VulkanEngine.h hat keinen Includeguard



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

    @Pixma Ausgerechnet die problematischen Funktionen bleiben im Header?

    lol, ich hab die problematischen Funktionen nun in die Cpp ausgelagert und die Fehler sind weniger geworden. Ich habe nun nurnoch zwei Fehler. Jedoch verstehe ich nicht, warum dies der Grund war.
    Ich hab ja eigentlich nur den Code refactored und keine großen Änderungen vorgenommen.
    Ich hab die Code Snippets in meinen vorherigen Post korrigiert.

    Nun hab ich noch Fehlermeldungen:

    1>------ Build started: Project: Testbed, Configuration: Debug Win32 ------
    1>DirectXEngine.cpp
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1801): error C2664: 'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'
    1>        with
    1>        [
    1>            _Ty=const DirectXEngine *
    1>        ]
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1799): note: Conversion loses qualifiers
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1865): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>        with
    1>        [
    1>            _Ty=DirectXRenderer
    1>        ]
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1866): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>        with
    1>        [
    1>            _Ty=DirectXRenderer
    1>        ]
    1>c:\users\dennis\source\repos\testbed\testbed\directxengine.cpp(15): note: see reference to function template instantiation 'std::shared_ptr<DirectXRenderer> std::make_shared<DirectXRenderer,const DirectXEngine*>(const DirectXEngine *&&)' being compiled
    1>DirectXRenderer.cpp
    1>Testbed.cpp
    1>VulkanEngine.cpp
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1801): error C2664: 'VulkanRenderer::VulkanRenderer(VulkanRenderer &&)': cannot convert argument 1 from '_Ty' to 'VulkanEngine *'
    1>        with
    1>        [
    1>            _Ty=const VulkanEngine *
    1>        ]
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1799): note: Conversion loses qualifiers
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1865): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const VulkanEngine*>(const VulkanEngine *&&)' being compiled
    1>        with
    1>        [
    1>            _Ty=VulkanRenderer
    1>        ]
    1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.16.27023\include\memory(1866): note: see reference to function template instantiation 'std::_Ref_count_obj<_Ty>::_Ref_count_obj<const VulkanEngine*>(const VulkanEngine *&&)' being compiled
    1>        with
    1>        [
    1>            _Ty=VulkanRenderer
    1>        ]
    1>c:\users\dennis\source\repos\testbed\testbed\vulkanengine.cpp(15): note: see reference to function template instantiation 'std::shared_ptr<VulkanRenderer> std::make_shared<VulkanRenderer,const VulkanEngine*>(const VulkanEngine *&&)' being compiled
    1>Generating Code...
    1>Done building project "Testbed.vcxproj" -- FAILED.
    ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
    
    

    DIe Fehlermeldungen liegen aber irgendwie mit dem memory file zusammen. Kann es sein, dass meine Smart Pointer noch nicht stimmen?

    @Schlangenmensch sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    @Pixma und deine VulkanEngine.h hat keinen Includeguard

    ich hab ihn nun mit kopiert. Den hatte ich beim kopieren aus meiner Solution übersehen.
    Danke für den Hinweis 🙂



  • @Pixma das ist dann wohl der erwartete const-Fehler



  • @Pixma Um die Antwort von @manni66 zu erweitern:

    in

    std::shared_ptr<IRenderer> DirectXEngine::create_renderer() const
    {
    	return directxrenderer_ptr{ std::make_shared<DirectXRenderer>(this) };
    
    }
    

    ist der this Pointer const. Du übergibst also ein const DirectXEngine* DirectXRenderer erwartet aber DirectXEngine*

    Das sagt dir auch die Compiler Fehlermeldung:

    'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'
    1> with
    1> [
    1> _Ty=const DirectXEngine *
    1> ]



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

    @Pixma Um die Antwort von @manni66 zu erweitern:

    in

    std::shared_ptr<IRenderer> DirectXEngine::create_renderer() const
    {
    	return directxrenderer_ptr{ std::make_shared<DirectXRenderer>(this) };
    
    }
    

    ist der this Pointer const. Du übergibst also ein const DirectXEngine* DirectXRenderer erwartet aber DirectXEngine*

    Das sagt dir auch die Compiler Fehlermeldung:

    'DirectXRenderer::DirectXRenderer(DirectXRenderer &&)': cannot convert argument 1 from '_Ty' to 'DirectXEngine *'
    1> with
    1> [
    1> _Ty=const DirectXEngine *
    1> ]

    Danke für deinen Hinweis. Das hab ich irgendwie garnicht gecheckt, dass das Problem das const ist. Ich hab das const entfernt und nun läuft es.
    Ich habe das Listing in meinen Post nochmals angepasst.
    Allen anderen auch. 🙂
    Ich merke, dass ich doch noch viel zu lernen hab.
    Ich will mir auf jedenfall nochmal das Thema Smart Pointer genauer anschauen und Design Patterns.



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

    So wenn ich deine Erklärung der Handles richtig verstanden hab, machen Handles dann mehr Sinn für verschiedene Objekte die im Speicher verwaltet werden.

    Handles machen z.B. Sinn:

    • Wenn man die eigene "Komponente" (Library, Module - wie auch immer du es nennen willst) vor falschem Input schützen muss. z.B. zwischen Kernel und Usermode. Der Kernel darf niemals seine eigenen Datenstrukturen kaputt machen, nur weil er z.B. einen falschen Zeiger vom Programm bekommen hat. Also verwendet er für seine Objekte Handles. Das Programm bekommt nur das Handle und der Kernel kann dann prüfen ob das Handle "valid" ist. Wenn nicht kann er einen Fehler zurückgeben anstatt irgendeinen Speicherbereich zu überschreiben.
    • Wenn man gar keine normalen Zeiger hergeben kann, weil der der das Objekt verwenden möchte gar keinen Zugriff auf den Speicher hat wo das eigentliche Objekt liegt. Beispielsweise hast du keinen direkten Zugriff auf Kernel-Speicher, Speicher der Grafikkarte oder Speicher auf einem Remote-System mit dem du über RPC kommunizierst. Also werden dort Handles verwendet.
    • Wenn man die eigentlichen Objekte im Speicher herumschieben möchte. Das wird in der Spieleprogrammierung soweit ich weiss öfters verwendet, damit man Objekte gleichen Typs in einem kompakten Array halten kann. Hat mit Caching zu tun - Daten aus einen zusammenhängenden Speicherbereich zu lesen oder modifizieren geht schneller als wenn man die selbe Anzahl Bytes an zigtausend Stellen verstreut im Speicher liest/modifiziert.
    • Wenn man die eigentlichen Objekte evtl. löschen möchte bevor das Programm das sie erzeugt hat sie explizit freigibt. Beispiel dafür wäre Tracing - also wenn man Daten über bestimmte Prozesse/die Ausführung eines Programms sammelt. Dabei kann es sinnvoll sein bestimmte Traces vorzeitig zu verwerfen - z.B. wenn sie zu alt sind und die Vermutung besteht dass das Programm einfach "vergessen" hat den Trace zu beenden.

    Jedes Objekt zum Beispiel ein Mesh oder eine Textur bekommt, dann ein Handle zugewiesen. Verwaltet wird das ganze dann über eine Handle Tabelle, wo jedes Objekt einem Handle zugeordnet werden kann.

    Würde ich nicht so machen. Wozu? Handles sind nichts was man einfach so als "best practice" überall drüberschütten sollte wo man es drüberschütten kann. Wenn du keinen konkreten Grund hast, dann lass es bleiben. Kostet nur unnötig Performance und macht den Code unnötig komplizierter. Und du willst BTW normalerweise auch gar nicht dass du grundverschiedene Objekte wie Texturen und Meshes über den selben Datentyp ansprichst. D.h. wenn du da aus irgend einem (guten) Grund Handles verwenden willst, dann solltest du verschiedene Handle-Typen dafür machen. Also z.B. eine kleine Klasse die nur einen Integer mit den Handle-Wert enthält. Im Programm sollte dann nur diese Klasse verwendet werden (=niemals der Integer-Wert direkt), damit das ganze type-safe bleibt.



  • @hustbaer
    ah, ok danke für deine Erklärung.
    Das hat mir enorm weiter geholfen, ich hab mich auch nochmal näher mit Handles nun beschäftigt.
    Was mir nun klar wurde, so wie ich Handles verstanden und verwendet habe ist einfach nur falsch.
    Ein Handle ist wohl kein Pointer, sondern nur ein Index auf eine Handletable.

    Zu deinem zweiten und dritten Punkt:
    @hustbaer sagte in Softwarearchitektur Initialisierung Grafik API mit Handle:

    • Wenn man gar keine normalen Zeiger hergeben kann, weil der der das Objekt verwenden möchte gar keinen Zugriff auf den Speicher hat wo das eigentliche Objekt liegt. Beispielsweise hast du keinen direkten Zugriff auf Kernel-Speicher, Speicher der Grafikkarte oder Speicher auf einem Remote-System mit dem du über RPC kommunizierst. Also werden dort Handles verwendet.
    • Wenn man die eigentlichen Objekte im Speicher herumschieben möchte. Das wird in der Spieleprogrammierung soweit ich weiss öfters verwendet, damit man Objekte gleichen Typs in einem kompakten Array halten kann. Hat mit Caching zu tun - Daten aus einen zusammenhängenden Speicherbereich zu lesen oder modifizieren geht schneller als wenn man die selbe Anzahl Bytes an zigtausend Stellen verstreut im Speicher liest/modifiziert.

    Wenn ich das so richtig verstehe, verwendet man Handles nur dazu in Kombination mit Handle Tables um verwendeten eigenen Speicher zu verwalten. Das ist ja auch das was du glaube mit den zwei Punkten ausdrücken wolltest, oder? Also Memory Allocation Strategien, um belegten Speicher zu defragmentieren zum Beispiel.
    Ich hab mir da noch ein YouTube Video angeschaut, wo Handles und Handle Tables näher erläutert werden:
    https://www.youtube.com/watch?v=Qsx5MYV985A



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

    Ein Handle ist wohl kein Pointer, sondern nur ein Index auf eine Handletable.

    Das ist eher ein Implementierungsdetail, ich würde mich da nicht so daran aufhängen.



  • @Mechanics
    Ja, stimmt schon.
    Der Knackpunkt ist auf jeden Fall dass wenn man "Handle" sagt es üblicherweise keine Garantie gibt dass es ein Zeiger ist. Weil man sonst halt auch einfach gleich Zeiger sagen könnte 🙂

    Ob diese Handlewerte/Zeigerwerte dann z.B. noch in einer Map/einem Table stehen, so dass man prüfen kann ob sie gültig sind, ist ein Implementierungsdetail.

    Worauf ich einfach hinaus wollte ist, dass es in C++ kaum Sinn macht nackte Zeiger als Handles zu verwenden. Man gewinnt nichts dadurch. Wenn der einzige Grund ist dass man das Layout des Objekts vor dem der es verwendet "verstecken" will, dann kann man das auch anders erreichen. z.B. indem man nur eine forward-declaration für alle Klassen in den öffentlichen Header-Files anbietet. Dann kann man weiter Zeigern arbeiten, muss aber nicht dauernd rumcasten.

    Man kann das dann natürlich immer noch als "Handle" bezeichnen, nur ist es mMn. sinnfrei das zu machen. Denn wenn ich weiss dass es ein Zeiger ist, dann verwende ich gleich den Begriff Zeiger und nicht den weniger spezifischen Begriff "Handle".


Anmelden zum Antworten