invalid use of incomplete type / forward declaration



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