invalid use of incomplete type / forward declaration



  • @daniel sagte in invalid use of incomplete type / forward declaration:

    @manni66 Weil die StateMachine alle möglichen States braucht. Also MainMenuState und später auch GameState usw

    Auf welche Frage von @manni66 ist das die Antwort?



  • @daniel sagte in invalid use of incomplete type / forward declaration:

    @manni66 Weil die StateMachine alle möglichen States braucht. Also MainMenuState und später auch GameState usw

    Im Header?



  • @daniel sagte in invalid use of incomplete type / forward declaration:

    ich möchte erstmal mit den Pointern völlig klarkommen

    Kommst du nicht. Deswegen gibt es Smartpointer.



  • Dieser Beitrag wurde gelöscht!


  • @Swordfish auf die Zweite
    @manni66 stimmt, nur in der implementierung
    @manni66 Okay aber ein Smartpointer ist doch nur ein Pointer mit automatisierter Garbage collection oder? Wie sähe eine implementierung davon in diesem Beispiel aus?



  • @daniel sagte in invalid use of incomplete type / forward declaration:

    nur ein Pointer mit automatisierter Garbage collection oder?

    Nein. Aber selbst wenn es so wäre: was möchtest du damit sagen?



  • @manni66 War eher eine Frage.
    Also das ist jetzt die StateMachine.h mit smartpointer:

    #pragma once
    #include "MainMenuState.h"
    #include <memory>
    #include "State.h"
    
    class StateMachine
    {
    private:
    	std::unique_ptr<State> currentState;
    public:
    	StateMachine();
    	void run();
    
    

    StateMachine.cpp:

    #include "StateMachine.h"
    
    StateMachine::StateMachine()
    {
    	currentState = std::move(std::unique_ptr<MainMenuState>(new MainMenuState));
    }
    
    void StateMachine::run()
    {
    	currentState->handleEvents(*this);
    }
    

    Und State.h, welche StateMachine includiert

    #pragma once
    #include "StateMachine.h"
    
    class State
    {
    public:
    	virtual void handleEvents(StateMachine &sm) = 0;
    };
    

    Das ergibt allerdings wieder folgende Fehler:

    In file included from MainMenuState.h:2:0,
                     from StateMachine.h:2,
                     from main.cpp:1:
    State.h:7:28: error: ‘StateMachine’ has not been declared
      virtual void handleEvents(StateMachine &sm) = 0;
                                ^~~~~~~~~~~~
    In file included from StateMachine.h:2:0,
                     from main.cpp:1:
    MainMenuState.h:8:20: error: ‘StateMachine’ has not been declared
      void handleEvents(StateMachine &sm);
                        ^~~~~~~~~~~~
    In file included from MainMenuState.h:2:0,
                     from StateMachine.h:2,
                     from StateMachine.cpp:1:
    State.h:7:28: error: ‘StateMachine’ has not been declared
      virtual void handleEvents(StateMachine &sm) = 0;
                                ^~~~~~~~~~~~
    In file included from StateMachine.h:2:0,
                     from StateMachine.cpp:1:
    MainMenuState.h:8:20: error: ‘StateMachine’ has not been declared
      void handleEvents(StateMachine &sm);
                        ^~~~~~~~~~~~
    StateMachine.cpp: In member function ‘void StateMachine::run()’:
    StateMachine.cpp:10:34: error: no matching function for call to ‘State::handleEvents(StateMachine&)’
      currentState->handleEvents(*this);
                                      ^
    In file included from MainMenuState.h:2:0,
                     from StateMachine.h:2,
                     from StateMachine.cpp:1:
    State.h:7:15: note: candidate: virtual void State::handleEvents(int&)
      virtual void handleEvents(StateMachine &sm) = 0;
                   ^~~~~~~~~~~~
    State.h:7:15: note:   no known conversion for argument 1 from ‘StateMachine’ to ‘int&’
    In file included from State.h:2:0,
                     from MainMenuState.h:2,
                     from MainMenuState.cpp:1:
    StateMachine.h:9:18: error: ‘State’ was not declared in this scope
      std::unique_ptr<State> currentState;
                      ^~~~~
    StateMachine.h:9:18: note: suggested alternative: ‘getdate’
      std::unique_ptr<State> currentState;
                      ^~~~~
                      getdate
    StateMachine.h:9:23: error: template argument 1 is invalid
      std::unique_ptr<State> currentState;
                           ^
    StateMachine.h:9:23: error: template argument 2 is invalid
    
    

    Habe ich vieleicht eine Falsche herangehensweise? Könnt ihr mir vieleicht ein extrem vereinfachtes Beispiel für eine StateMachine zeigen?



  • Auch für Referenzen reicht eine Vorwärtsdeklaration. Entferne das include StateMachine aus dem State Header.



  • #include "MainMenuState.h" warum steht das immer noch im Header?



  • Okay hab das gemacht und es funktioniert alles. Vielen Dank!



  • @daniel sagte in invalid use of incomplete type / forward declaration:

    Wie sähe eine implementierung davon in diesem Beispiel aus?

    context.h

    #pragma once
    
    #include <memory>
    
    struct context_t
    {
    	virtual void present() const = 0;
    	virtual std::unique_ptr<context_t> handle_input() = 0;
    	virtual ~context_t() = default;
    };
    

    title_screen_context.h

    #pragma once
    
    #include "context.h"
    
    struct title_screen_context_t final : context_t
    {
    	virtual void present() const override;
    	virtual std::unique_ptr<context_t> handle_input() override;
    };
    

    title_screen_context.cpp

    #include "title_screen_context.h"
    #include "main_menu_context.h"
    
    #include <iostream>
    
    void title_screen_context_t::present() const
    {
    	std::cout << "title screen\n\npress enter\n\n";
    }
    
    std::unique_ptr<context_t> title_screen_context_t::handle_input()
    {
    	std::cin.get();
    	return std::make_unique<main_menu_context_t>();
    }
    

    main_menu_context.h

    #pragma once
    
    #include "context.h"
    
    struct main_menu_context_t final : context_t
    {
    	virtual void present() const override;
    	virtual std::unique_ptr<context_t> handle_input() override;
    };
    

    main_menu_context.cpp

    #include "main_menu_context.h"
    #include "credits_context.h"
    
    #include <iostream>
    
    void main_menu_context_t::present() const
    {
    	std::cout << "[1] menu item 1\n[2] menu item 2\n[3] menu item 3\n[0] exit\n";
    }
    
    std::unique_ptr<context_t> main_menu_context_t::handle_input()
    {
    	int choice;
    	std::cin >> choice;
    
    	switch (choice) {
    	case 0:
    		return std::make_unique<credits_context_t>();
    	case 1:
    		return std::make_unique<main_menu_context_t>();  // todo: context fuer menuepunkt 1
    	case 2:
    		return std::make_unique<main_menu_context_t>();  // todo: context fuer menuepunkt 2
    	case 3:
    		return std::make_unique<main_menu_context_t>();  // todo: context fuer menuepunkt 3
    	}
    
    	return std::make_unique<main_menu_context_t>();
    }
    

    credits_context.h

    #pragma once
    
    #include "context.h"
    
    struct credits_context_t final : context_t
    {
    	virtual void present() const override;
    	virtual std::unique_ptr<context_t> handle_input() override;
    };
    

    credits_context.cpp

    #include "credits_context.h"
    #include "main_menu_context.h"
    
    #include <iostream>
    
    void credits_context_t::present() const
    {
    	std::cout << "Credits: ...\n\nReally Exit? [y|n]\n";
    }
    
    std::unique_ptr<context_t> credits_context_t::handle_input()
    {
    	char choice;
    	std::cin >> choice;
    
    	if (choice == 'y')
    		return nullptr;
    
    	return std::make_unique<main_menu_context_t>();
    }
    

    machine.h

    #pragma once
    
    #include <memory>
    
    #include "title_screen_context.h"
    
    struct machine_t final
    {
    	std::unique_ptr<context_t> context = std::make_unique<title_screen_context_t>();
    	void run();
    };
    

    machine.cpp

    #include "machine.h"
    
    void machine_t::run()
    {
    	while (context) {
    		context->present();
    		context.reset(context->handle_input().release());
    	}
    }
    

    main.cpp

    #include "machine.h"
    
    int main()
    {
    	machine_t{}.run();
    }
    


  • @Swordfish Oh wow danke, damit muss ich mich jetzt erstmal beschäftigen



  • @Swordfish

    = default 
    

    Das macht die struct trivial glaube ich, warum benutzt du das?

    context.reset(context->handle_input().release());
    

    Was genau macht diese Zeile?



  • @daniel sagte in invalid use of incomplete type / forward declaration:

    = default 
    

    Das macht die struct trivial glaube ich, warum benutzt du das?

    Was heißt "macht trivial"?
    Warum er das macht: Ein destructor wird hier eigentlich nicht benötigt, muss aber doch angegeben werden, da der destructor virtual sein muss.
    Daher das für jeden sofort ersichtliche "virtual ~dtor() = default" statt einer leeren Definition irgendwo im cpp.

    context.reset(context->handle_input().release());
    

    Was genau macht diese Zeile?

    Macht context zum Owner von dem handle_input-Rückgabewert. Kann auch nullptr sein, daher hat er den Besitz nicht gemoved.
    Sehr kompakt, allerdings fand ich das trotzdem auch schwer zu lesen.

    Wobei...eigentlich auch wurscht, nee, verstehe ich doch nicht, warum nicht einfach "context = context->handle_input();"



  • @Jockelx Okay also wirkt

     fnc() = default;
    

    genauso wie ?

    fnc() {};
    

    Eine andere Frage; SFML arbeitet sehr oft mit sf::Vector2f. Ich brauche für meine Koordinaten usw aber nur short int. Wenn ich short int benutzen würde, um die Klarheit des Zwecks und die Speichernutzung zu verbessern, müsste ich oft datentypen konvertieren. Die Frage ist also, was effizienter ist, und ob das überhaupt einen nennenswerten Unterschied macht. Bei zum Beispiel einer etwas größeren Partikel Simulation.



  • @daniel sagte in invalid use of incomplete type / forward declaration:

    @Jockelx Okay also wirkt

     fnc() = default;
    

    genauso wie ?

    fnc() {};
    

    Nee, ich kann mir aber nur merken: Am besten weg lassen, dann defaulten, dann definieren. 😉
    Die Unterschiede haben damit zu tun, welche Funktionen automatisch generiert werden (move-ctor,...), wie member initialisiert werden und ob der Typ wie ein POD funktioniert (war das mit dem trivial gemeint?).
    Müsste ich jetzt mühsam zusammen suchen. Im Kopf habe ich das nicht.



  • @Jockelx Achso; ich habe gelesen, dass das Zuweisen eines Destruktors zu default die Klasse oder Struct non-trivial macht

    "This is a completely different question when asking about constructors than destructors.

    If your destructor is virtual, then the difference is negligible, as Howard pointed out. However, if your destructor was non-virtual, it's a completely different story. The same is true of constructors.

    Using = default syntax for special member functions (default constructor, copy/move constructors/assignment, destructors etc) means something very different from simply doing {}. With the latter, the function becomes "user-provided". And that changes everything.

    This is a trivial class by C++11's definition:

    struct Trivial
    {
    int foo;
    };

    If you attempt to default construct one, the compiler will generate a default constructor automatically. Same goes for copy/movement and destructing. Because the user did not provide any of these member functions, the C++11 specification considers this a "trivial" class. It therefore legal to do this, like memcpy their contents around to initialize them and so forth.

    This:

    struct NotTrivial
    {
    int foo;

    NotTrivial() {}
    };

    As the name suggests, this is no longer trivial. It has a default constructor that is user-provided. It doesn't matter if it's empty; as far as the rules of C++11 are concerned, this cannot be a trivial type.

    This:

    struct Trivial2
    {
    int foo;

    Trivial2() = default;
    };

    Again as the name suggests, this is a trivial type. Why? Because you told the compiler to automatically generate the default constructor. The constructor is therefore not "user-provided." And therefore, the type counts as trivial, since it doesn't have a user-provided default constructor.

    The = default syntax is mainly there for doing things like copy constructors/assignment, when you add member functions that prevent the creation of such functions. But it also triggers special behavior from the compiler, so it's useful in default constructors/destructors too."
    Hier ist der Link
    Vieleicht habe ich da was falsch verstanden



  • @daniel

    ich habe gelesen, dass das Zuweisen eines Destruktors zu default die Klasse oder Struct non-trivial macht

    Wo steht das da? Die Beispiele beziehen sich zwar auf den Konstruktor, aber da wird gesagt das =default das "trivial" macht.

    Die Vorteile davon kann man hier nachlesen: https://docs.microsoft.com/en-us/cpp/cpp/trivial-standard-layout-and-pod-types?view=vs-2019#trivial-types



  • @daniel Ich verstehe vor allem nicht, warum Du Dich das bei diesem Beispiel im Bezug auf context_t überhaupt fragst. Was würd's Dir bringen wenn context_t standard layout wäre? Aber auch die Frage ob wegen einem defaulted destructor nun standard layout ist oder nicht ... hell, das ding ist abstrakt. Anyway. Die Regeln:

    [class.prop]/3:

    A class S is a standard-layout class if it:

    (3.1) has no non-static data members of type non-standard-layout class (or array of such types) or reference,

    (3.2) has no virtual functions 1) and no virtual base classes,

    (3.3) has the same access control for all non-static data members,

    (3.4) has no non-standard-layout base classes,

    (3.5) has at most one base class subobject of any given type,

    (3.6) has all non-static data members and bit-fields in the class and its base classes first declared in the same class, and

    (3.7) has no element of the set M(S) of types as a base class, where for any type X, M(X) is defined as follows.105 [ Note: M(X) is the set of the types of all non-base-class subobjects that may be at a zero offset in X. — end note ]

    (3.7.1) If X is a non-union class type with no (possibly inherited) non-static data members, the set M(X) is empty.

    (3.7.2) If X is a non-union class type with a non-static data member of type X0 that is either of zero size or is the first non-static data member of X (where said member may be an anonymous union), the set M(X) consists of X0 and the elements of M(X0).

    (3.7.3) If X is a union type, the set M(X) is the union of all M(Ui) and the set containing all Ui, where each Ui is the type of the ith non-static data member of X.

    (3.7.4) If X is an array type with element type Xe, the set M(X) consists of Xe and the elements of M(Xe)

    (3.7.5) If X is a non-class, non-array type, the set M(X) is empty.

    1.) also nein, context_t ist natürlich nicht standart-layout. Aber wen kratzt es? Es wird nie-nicht Instanzen von context_t geben.


Anmelden zum Antworten