Event-Container in C++ 11

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Event-Container in C++ 11

Beitrag von Niki »

Hallo Leute,

ich versuche mit C++ 11 einen Event-Container zu bauen (à la C#). Über operator += kann man einen Event-Handler zum Container hinzufügen, und per operator -= kann man einen Event-Handler aus dem Container entfernen. Gespeichert weden die Event-Handler in einem std::vector<std::function<...>>.

Um einen Event-Handler zu entfernen muss ich natürlich irgendwie std::functions auf Gleichheit prüfen. Bekannterweise haben std::functions wegen irgendeinem, mir unbekannten Problem aber keine Vergleichsoperatoren. Also gehe ich über std::function::target<T>() und std::function::target_type(). Das Problem ist, dass ich mittlerweile so verwirrt bin, dass ich mich dabei komplett unsicher fühle. Ist diese Vorgehensweise okay, oder verursacht die Probleme, die ich grad nicht sehe?

Im folgenden der Code, den ich gerade zum Testen benutze. Der funktioniert, aber ist er, bezüglich des Funktionsvergleichs auch problemfrei...?!


WARNUNG: Selbst wenn sich herausstellen sollte, dass die Funktionsvergleiche im Code unten okay sind, so ist zu bemerken das das TEventContainer-Template noch nicht fertig implementiert ist. Wenn zum Beispiel ein Event-Handler sich selbst entfernt, dann kracht es im Karton. Also bitte nicht blind benutzen.

Code: Alles auswählen

#include <vector>
#include <functional>
#include <iostream>

// Typ für einen Beispiel Event-Handler.
using MyEventHandler = std::function<void (void * pParam1, void * pParam2)>;

// Event Container
template <class _TEventHandler>
class TEventContainer
{
    public:
        TEventContainer<_TEventHandler> & operator -= (_TEventHandler eventHandler)
        {
            size_t numEventHandlers = m_eventHandlers.size();
            for (size_t i = 0; i < numEventHandlers; i++)
            {
                // Hier der Funktionsvergleich
                if (m_eventHandlers[i].target<_TEventHandler>() == eventHandler.target<_TEventHandler>()
                &&  m_eventHandlers[i].target_type() == eventHandler.target_type())
                {
                    m_eventHandlers.erase(m_eventHandlers.begin() + i);
                    break;
                }
            }
            return *this;
        }

        TEventContainer<_TEventHandler> & operator += (_TEventHandler eventHandler)
        {

            m_eventHandlers.push_back(eventHandler);
            return *this;
        }

    public:
        template <class ... _TArgs>
        void operator () (_TArgs ... args)
        {
            size_t numEventHandlers = m_eventHandlers.size();
            for (size_t i = 0; i < numEventHandlers; i++)
            {
                m_eventHandlers[i](args ...);
            }
        }

    private:
        std::vector<_TEventHandler>     m_eventHandlers;
};

// Test-Code
int main(int argc, char * argv[])
{
    MyEventHandler lambdaHandler = [] (void * pParam1, void * pParam2) 
    {
        std::cout << "called" << std::endl;
    };

    TEventContainer<MyEventHandler> eventContainer;
    eventContainer += lambdaHandler;
    eventContainer += lambdaHandler;
    eventContainer -= lambdaHandler;
    eventContainer += lambdaHandler;
    eventContainer -= lambdaHandler;

    // Alle Event-Handler aufrufen
    eventContainer(nullptr, nullptr);

    return 0;
}
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

Ok, nach noch mehr Googelei habe ich dieses Paper gefunden: http://www.open-std.org/jtc1/sc22/wg21/ ... /n1667.pdf

Es liest sich für mich so, als ob std::function::target<T>() und std::function::target_type() für exakt diesen Fall in den Standard aufgenommen wurden. Ich werde jetzt also erstmal damit weitermachen, es sei denn hier im Forum schreit noch irgendwer "Halt! Problem!" :-)
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Event-Container in C++ 11

Beitrag von CodingCat »

Niki hat geschrieben:Ok, nach noch mehr Googelei habe ich dieses Paper gefunden: http://www.open-std.org/jtc1/sc22/wg21/ ... /n1667.pdf

Es liest sich für mich so, als ob std::function::target<T>() und std::function::target_type() für exakt diesen Fall in den Standard aufgenommen wurden. Ich werde jetzt also erstmal damit weitermachen, es sei denn hier im Forum schreit noch irgendwer "Halt! Problem!" :-)
Prinzipiell: Ja, damit bekommst du einen Vergleich implementiert. So wie du das im eingangs geposteten Code-Ausschnitt tust, funktioniert das allerdings nicht. target<T>() erwartet für T den Typ des gespeicherten Objekts, z.B. einen Funktionszeigertyp, einen Member-Funktionszeigertyp oder gar einen anonymen Lambda-Typ. Du brauchst also in deiner erase()-Funktion noch den exakten Typ der übergebenen Funktion, nicht den std::function-verpackten Typ. Den exakten Typ bekommst du aber nur per Template Argument Deduction, du solltest die zu entfernende Funktion also über Perfect Forwarding annehmen.

Nachtrag: Hast du den Typ, kannst du dir den Typvergleich sparen: target<T>() gibt bei Mismatch bereits nullptr zurück. Vergleichen solltest du auch nicht die Zeiger, da die sich für JEDES std::function-Objekt unterscheiden, sondern *target() mit der über Forwarding angenommenen zu entfernenden Funktion.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

Danke CodingCat!

Diese C++ 11-Features sind für mich noch recht neu, aber so langsam geht's voran. Trotzdem habe da noch viel zu lernen. Ich schaue erstmal selbst ob ich das umgesetzt kriege, aber es könnte sein das ich später um ein Beispiel bitte :-) (EDIT: mit Perfect Forwarding habe ich mich noch garnicht beschäftigt... muss ich erst mal lesen)
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Event-Container in C++ 11

Beitrag von dot »

Ich denk, dass das OK ist. Ich hatte auch mal eine Periode, wo ich stark von einem relativ ähnlichen Konstrukt Gebrauch gemacht habe. Zunächst mal hier der Hinweis, dass du das Hinzufügen und Entfernen von Event Handlern unbedingt threadsafe machen solltest, da dir das früher oder später sonst zum Verhängnis werden wird. Außerdem gibt es einen ganz wichtigen Fall, der mich damals auf die Nase fallen lies, den du da oben, soweit ich das sehen kann, auch nicht bedacht hast: Meiner Erfahrung nach, hat man relativ häufig mal einen EventHandler, der sich als Reaktion auf ein Event selbst aus der Liste entfernt. Das Problem ist nun: Wenn das Event ausgelöst wird, iteriert dein Event Container über alle Event Handler und ruft jeden auf. Wenn einer dieser Event Handler sich nun aber selbst aus der Liste löscht, wird der Iterator, den der Event Container zum Iterieren über alle Event Handler verwendet, ungültig (da du einen std::vector verwendest) und die nächste Iteration der Schleife crashed (bestenfalls). Um dieses Problem zu lösen, wirst du wohl oder übel statt dem std::vector eine std::list oder std::forward_list verwenden müssen. Um das mit der Threadsafety effizient hinzubekommen, wirst du vermutlich überhaupt einen eigenen Container basteln müssen...

Abgesehen von all dem, bin ich persönlich zu der Ansicht gelangt, dass solche Events rein prinzipiell keine gute Idee sind. Sie führen früher oder später nur zu einem völlig undurchsichtigen und unkontrollierbaren Gewirr von Kontrollfluss. Und in der Regel hat man eigentlich eh nie mehr als einen Listener, denn sobald man mehr als einen Listener auf dem selben Event würde haben wollen, ist das normalerweise Anzeichen eines Designproblems. Wenn man aber nur einen Listener braucht, dann ist der ganze Listenkram und der damit verbundene Overhead völlig unnötig...
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

Danke für dein Feedback, dot.

Wie üblich habe ich nichts über den Einsatz des Event-Containers geschrieben (den ich mittlerweile in TDelegateContainer umbenannt habe, weil's klarer ist). Also will ich das mal nachholen.

Ich habe keineswegs vor meinen gesamten Code mit einem möglicherweise unverständlichen und komplizierten Netz aus Delegates zu versehen. Zu diesem Zeitpunkt sollen die Delegates nur für GUI-Events dienen. Klar könnte man das auch mit Listener-Klassen lösen, aber wenn durch Vererbung von GUI-Controls neue Events hinzukommen, dann finde ich die Variante eher unschön. Damit würden auch sämtliche Listener-Methoden aufgerufen werden die für einen bestimmten Fall völlig uninteressant sind (z.B. onMouseMove wird aufgerufen wenn mich nur onClick interessiert). Persönlich finde ich die Lösung in Windows Forms mit C# sehr schön, und ich versuche diese Funktionalität in C++ umzusetzen.

Über Thread-Safety habe ich schon nachgedacht, bin aber momentan noch unentschlossen ob ich es einbauen werde. Im Spezialfall einer GUI sollte ich eigentlich in der Lage sein Threading-Probleme einfach zu vermeiden. Sollte ich aber doch irgendwann die Delegates für andere Zwecke benutzen, dann wird das Thema natürlich wieder aktuell.

Desweiteren schreibst du von Event-Handlern die den Inhalt des Event-Containers verändern, zum Beispiel indem sie sich selbst aus dem Container entfernen. Oder noch fieser, wenn sie ein Objekt zerstören welches Handler in demselben Event-Container hat. Diese Fehler sind schwer zu finden und zu fixen. Probleme dieser Art sind der Grund für die "WARNUNG" in meiner ersten Post. Es gibt verschiedene Möglichkeiten diese Probleme zu beheben. Beispielsweise könnte man eine Kopie des Handler-Arrays erstellen, und dann die Handler über diese Kopie aufrufen. Dadurch können die aufgerufenen Handler neue andere Handler zum Container hinzufügen ohne das es Probleme gibt. Gleichzeitig kann der Call-Code prüfen ob der Handler auch im "Original"-Array existiert. Wenn nicht, dann darf der Handler nicht aufgerufen werden (das ist der Remove-Fall). Ob ich aber diesen Weg gehe, oder einen anderen, habe ich zu diesem Zeitpunkt noch nicht entschieden. Als erstes möchte ich die C++11-spezifischen Probleme lösen.
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

So genug jetzt! Hab' die Faxen dicke! :-D

Die große C++ 11 Leseaktion heute war natürlich sinnvoll, denn das ist wertvolles Wissen. Aber bei dem Event-Container stoße ich an so viele Kanten, dass ich nicht mal ordentlich die Probleme aufzählen könnte. Deshalb werde ich jetzt einen ganz einfachen Weg gehen.

Folgende Hierarchie von GUI Elementen sei gegeben:

Code: Alles auswählen

// Alle GUI Elemente leiten hiervon ab.
class View; 

// Alle Controls leiten hiervon ab
class Control : public View; 

// Eine Buttonklasse eben
class Button : public Control; 
Zu jeder dieser Klassen wird es eine struct mit std::functions geben. Etwa so:

Code: Alles auswählen

struct ViewDelegate
{
    std::function<void (View * pSender, SizeChangedEventArgs & rEventArgs)> SizeChanged;
    std::function<void (View * pSender, MouseEventArgs & rEventArgs)> MouseDown;
};

struct ControlDelegate
{
};

struct ButtonDelegate
{
    std::function<void (Button * pSender, EventArgs & rEventArgs)> Clicked;
};
Jede GUI Elementklasse wird einen Setter für die zugehörige Delegate-Struktur haben, welche per unique_ptr gesetzt wird. Das ganze so, dass der Button aufgrund der Vererbung drei Delegate-Strukturen hat, ViewDelegate, ControlDelegate, und ButtonDelegate. Diese Delegate-Strukturen erben also nicht voneinander, wodurch ich mir eine irre dynamic_casterei oder ähnliches spare.

Im Vergleich zu den Event-Containern ist dieses System stark vereinfacht, aber ich will ja auch mal vorwärts kommen. Zudem denke ich, dass es mehr als ausreichend sein wird.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Event-Container in C++ 11

Beitrag von dot »

Niki hat geschrieben:Ich habe keineswegs vor meinen gesamten Code mit einem möglicherweise unverständlichen und komplizierten Netz aus Delegates zu versehen. Zu diesem Zeitpunkt sollen die Delegates nur für GUI-Events dienen.
Als ich obige Ausführungen verfasst hab, hab ich ganz besonders an GUI Code gedacht... ;)
Niki hat geschrieben:Klar könnte man das auch mit Listener-Klassen lösen, aber wenn durch Vererbung von GUI-Controls neue Events hinzukommen, dann finde ich die Variante eher unschön. Damit würden auch sämtliche Listener-Methoden aufgerufen werden die für einen bestimmten Fall völlig uninteressant sind (z.B. onMouseMove wird aufgerufen wenn mich nur onClick interessiert).
Wieso?
Niki hat geschrieben:Persönlich finde ich die Lösung in Windows Forms mit C# sehr schön, und ich versuche diese Funktionalität in C++ umzusetzen.
Ich persönlich fand die Lösung in Windows Forms früher mal auch ganz OK. Bis zu dem Zeitpunkt da ich mal eine Sekunde drüber nachgedacht hab. Seither find ich sie grauenhaft (nicht, dass andere GUI Toolkits da irgendwas besser machen würden). Überleg mal, was es bedeutet, dass jedes Control all diese ganzen Events hat. Die Systems.Windows.Forms.Control Basisklasse allein hat 69 nonstatic Events wenn ich mich nicht verzählt hab, System.Windows.Controls.Control hat sogar in der Gegend von 110. Bedenke weiter, dass kaum eine Anwendung jemals mehr als auch nur eine Handvoll der Events, die so ein Control anbietet, verwenden wird. Das heißt also, dass jede einzelne Instanz irgendeines Control mehr als 69 Listen enthält wobei vermutlich 99% davon immer leer sind. Selbst wenn für jede dieser Liste nur ein einzelner Pointer verwedet würde, haben wir auf einem 64 Bit System damit bereits 552 Byte – in Worten: Mehr als ein verdammtes halbes Kilobyte – an unbenutzten Listenpointern in jeder einzelnen Instanz irgendeines Control. Jedesmal, wenn so ein Event ausgelöst wird, wird erstmal eine virtuelle Methode aufgerufen, die dann eine Liste checked, nur um festzustellen, dass diese leer ist. Es tut mir leid, aber ich sehe wirklich nicht, wie man Overhead dieser Größenordnung jemals rechtfertigen oder gar als "schön" bezeichnen will... ;)
Niki hat geschrieben:Über Thread-Safety habe ich schon nachgedacht, bin aber momentan noch unentschlossen ob ich es einbauen werde. Im Spezialfall einer GUI sollte ich eigentlich in der Lage sein Threading-Probleme einfach zu vermeiden.
Gerade bei GUI würde ich Threading-Probleme erwarten, denn gerade da hat man wohl sehr oft den Fall, dass es einen Message Pump Thread gibt, aus dem die Events kommen und andere Threads, die im Hintergrund Arbeit verrichten...
Niki hat geschrieben:Desweiteren schreibst du von Event-Handlern die den Inhalt des Event-Containers verändern, zum Beispiel indem sie sich selbst aus dem Container entfernen. Oder noch fieser, wenn sie ein Objekt zerstören welches Handler in demselben Event-Container hat. Diese Fehler sind schwer zu finden und zu fixen. Probleme dieser Art sind der Grund für die "WARNUNG" in meiner ersten Post. Es gibt verschiedene Möglichkeiten diese Probleme zu beheben. Beispielsweise könnte man eine Kopie des Handler-Arrays erstellen, und dann die Handler über diese Kopie aufrufen. Dadurch können die aufgerufenen Handler neue andere Handler zum Container hinzufügen ohne das es Probleme gibt. Gleichzeitig kann der Call-Code prüfen ob der Handler auch im "Original"-Array existiert. Wenn nicht, dann darf der Handler nicht aufgerufen werden (das ist der Remove-Fall). Ob ich aber diesen Weg gehe, oder einen anderen, habe ich zu diesem Zeitpunkt noch nicht entschieden. Als erstes möchte ich die C++11-spezifischen Probleme lösen.
Ich wollt es nur erwähnen, da ich damals im ersten Anlauf auf diese gemeinen Fälle vergessen hab und auf die Nase gefallen bin. ;)
Wie gesagt, die einfachste Lösung wäre wohl, statt einem Array eine verlinkte Liste zu verwenden.

Niki hat geschrieben:Zu jeder dieser Klassen wird es eine struct mit std::functions geben.

[...]

Jede GUI Elementklasse wird einen Setter für die zugehörige Delegate-Struktur haben, welche per unique_ptr gesetzt wird. Das ganze so, dass der Button aufgrund der Vererbung drei Delegate-Strukturen hat, ViewDelegate, ControlDelegate, und ButtonDelegate. Diese Delegate-Strukturen erben also nicht voneinander, wodurch ich mir eine irre dynamic_casterei oder ähnliches spare.

Im Vergleich zu den Event-Containern ist dieses System stark vereinfacht, aber ich will ja auch mal vorwärts kommen. Zudem denke ich, dass es mehr als ausreichend sein wird.
Damit näherst du dich auch schon der Lösung, die ich im Moment verwende. Ich würde vorschlagen, statt diesen structs aus delegates einfach entsprechende Interfaces zu definieren und fertig. Einfach und schlank. Wenn ein User tatsächlich den vollen Event-Handler-Listen-Tanz aufführen will, kann er das immer noch tun, indem er einfach eine entsprechende Klasse bastelt, die das Interface auf Events mapped. Zumindest zwingst du so nicht jeden einzelnen User dazu, dir jedes Event jedes einzelnen Control einzeln abzukaufen (inkl. threadsafety etc.). Auch sparst du dir damit wohl einen ganzen Berg dynamischer Speicherallokationen. Ganz im Geiste von C++: You don't pay for what you don't use... ;)
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

dot hat geschrieben:Wieso?
Vielleicht ein Missverständnis. Mit Listenerklassen meinen wir sicher beide eine Klasse mit einer größeren Ansammlung virtueller Funktionen. Natürlich müssen diese an den entsprechenden Stellen auch aufgerufen werden. Im Falle meiner ursprünglicen Post konnte dann jedes GUI-Element mehrere Listener haben, wodurch mehrere virtuelle Funktionen an den entsprechenden Stellen aufgerufen werden können, egal ob den Listener dieser Event interessiert oder nicht. Mit meinem ersten Ansatz tue ich das nur wenn Interesse an einem Event besteht (dafür ist es dann sehr viel teurer). Auf der anderen Seite hat man aber meist eh nur einen Listener, also ist das alles sehr theoretisch.
dot hat geschrieben:Es tut mir leid, aber ich sehe wirklich nicht, wie man Overhead dieser Größenordnung jemals rechtfertigen oder gar als "schön" bezeichnen will... ;)?
Das ist sicherlich eine Geschmacksfrage. Es gibt Situationen wo mir persönlich der Komfort wichtiger ist als der Overhead, und ich finde das C# Event-System sehr komfortabel. Da verstrickt sich bei mir auch nichts, da die Events in der entsprechenden Form-Klasse verarbeitet werden. Also... ich mag's ;-)
dot hat geschrieben:Gerade bei GUI würde ich Threading-Probleme erwarten, denn gerade da hat man wohl sehr oft den Fall, dass es einen Message Pump Thread gibt, aus dem die Events kommen und andere Threads, die im Hintergrund Arbeit verrichten...
Mir ist grad nicht klar, warum ein Thread direkt Events von einer GUI empfangen soll. Ich meine indirekt, ja, aber nicht direkt. Hast du vielleicht ein konkretes Situationsbeispiel wo du solche Probleme siehst?
dot hat geschrieben:Zumindest zwingst du so nicht jeden einzelnen User dazu, dir jedes Event jedes einzelnen Control einzeln abzukaufen (inkl. threadsafety etc.).
Geschmackssache, denke ich. Beides hat Vor- und Nachteile. Mit std::function kann ich zum Beispiel Events per Lambda implementieren. Klar es geht auch ohne, aber in manchen Situation finden ich es komfortabel. Allerdings wäre es auch nicht mein Untergang auf Lambdas zu verzichten. Des weiteren kann ich mit einzelen std::functions einen Event abbestellen oder auf einen anderen Handler umbiegen. Was natürlich nicht bedeutet, dass man das resultierende Ziel nicht auch mit Interfaces verwirklichen kann. Ich stehe also auf dem Standpunkt, dass keine der beiden Varianten wirklich besser oder schlechter ist als die andere. Geschmackssache eben.
dot hat geschrieben:Auch sparst du dir damit wohl einen ganzen Berg dynamischer Speicherallokationen. Ganz im Geiste von C++: You don't pay for what you don't use... ;)
Da stimme ich dir vollkommen zu. Das ist auch der Grund, warum ich mich noch nicht ganz gegen Interfaces entschieden habe. Die Implementation geht erst heute los, also werde ich mich irgendwann im Laufe des Tages entscheiden. Momentan sind Strukturen mit std::functions jedoch am gewinnen :-)
Benutzeravatar
Schrompf
Moderator
Beiträge: 4864
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Event-Container in C++ 11

Beitrag von Schrompf »

Ich habe mich damals auch gegen Interfaces entschieden. Ich empfand es als zu starre Kopplung, wenn ich jede Klasse, die irgendwie auf GUI-Interaktion reagiert, von einem oder gar 110 Listenern ableiten muss. Daher rechnet bei mir ganz banal boost::signal2. Ich habe damit allerdings auch nie GUI-Größen wie einen InGame-Editor vor, sondern nur InGame-GUIs. Bei denen ist fluffiges Aussehen wichtiger als Tab-Reihenfolge oder andockbare Tool Windows.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

Schrompf hat geschrieben:... allerdings auch nie GUI-Größen wie einen InGame-Editor vor, sondern nur InGame-GUIs. Bei denen ist fluffiges Aussehen wichtiger als Tab-Reihenfolge oder andockbare Tool Windows.
Eine GUI, die auch für einen Editor brauchbar ist, wäre für mich prima. Und ich arbeite darauf zu. Aber auch eine in-game GUI kann ja, je nach Spiel, durchaus komplex sein. Ich bin Fan von (A)RPGs was in der Regel komplexere GUIs mit sich bringt.
Jedenfalls werde ich mir sehr viel Zeit nehmen, damit die resultierenden GUIs sowohl benutzerfreundlich als auch performant sind. Momentan sitze ich noch an der Layout Engine. Vielleicht gibt's bald ein paar Screenshots, ich muss erst noch einen Renderer basteln, der wenigstens erstmal gefüllte Rechtecke zeichnen kann.
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

Ich habe mich nun endgültig für Strukturen mit std::functions entschieden. Hier ein Beispiel warum:

Ich habe ein typsiches in-game Menü, welches z.B. in der Klasse InGameMenu implementiert ist. Dieses Menü soll nun folgende Buttons haben:

Weiterspielen
Spielstand speichern
Spielstand laden
Optionen
Hauptmenü

Mit Interfaces habe ich nun zwei Möglichkeiten (entschuldigt, falls ich Möglichkeiten übersehen habe):

(1) Ich erstelle fünf Klassen die von ButtonInterface erben und implementiere die für jeden einzelnen Button. Für mich nicht so pralle, besonders weil ich einer bin der, mit Ausnahmen, auf eine Klasse pro Datei steht (von enums, den meisten structs und Exception-Klassen einmal abgesehen).

(2) Ich erstelle nur eine Klasse die von ButtonInterface ableitet, oder noch besser ich lasse InGameMenu von ButtonInterface erben. Dadurch habe ich genau eine Listenermethode die aufgerufen wird, wenn ein Button geklickt wird. Das bedeutet if/else um rauszufinden welcher Button denn nun gedrückt wurde. Bei einem simplen Menü wie in diesem Beispiel mag das noch gehen, aber wenn's komplexer wird muss ich mir das nicht geben. Dann doch lieber Overhead.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Event-Container in C++ 11

Beitrag von CodingCat »

Und wie sieht die Struktur in deinem Beispiel aus?
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

CodingCat hat geschrieben:Und wie sieht die Struktur in deinem Beispiel aus?
Da kann ich dir jetzt nur Pseuo-code anbieten, der es hoffentlich verdeutlicht. Wenn nicht, dann kannst du ja fragen :-)

Beispiel-Delegate:

Code: Alles auswählen

struct ButtonDelegate
{
    std::function<void (View * pSender)> Clicked;
    // ... und was sonst noch bei Buttons interessant sein mag.
};
Beispiel-Benutzung:

Code: Alles auswählen

void InGameMenu::Initialize()
{
    Button * pContinueButton = createButton(...)
    ButtonDelegate * pDelegate = createDelege(...)
    pDelegate->Clicked = ContinueButtonClicked;
	
    Button * pSaveButton = createButton(...)
    ButtonDelegate * pDelegate = createDelege(...)
    pDelegate->Clicked = SaveButtonClicked;

    Button * pLoadButton = createButton(...)
    ButtonDelegate * pDelegate = createDelege(...)
    pDelegate->Clicked = LoadButtonClicked;

    Button * pOptionsButton = createButton(...)
    ButtonDelegate * pDelegate = createDelege(...)
    pDelegate->Clicked = OptionsButtonClicked;

    Button * pMainMenuButton = createButton(...)
    ButtonDelegate * pDelegate = createDelege(...)
    pDelegate->Clicked = MainMenuButtonClicked;
}

void InGameMenu::ContinueButtonClicked(View * pSender)
{
}

// dito für den Rest
EDIT: Ich habe jetzt vergessen den Buttons die Delegates zuzuweisen, aber ich denke das Prinzip ist trotzdem klar.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Event-Container in C++ 11

Beitrag von CodingCat »

Verstehe. Nunja, auch nicht speichereffizient, aber darauf kommt es hier wohl kaum an. Wo hier gerade alle an UIs schreiben, juckt es mich sehr in den Fingern, mehr zu zustandsloser UI zu schreiben - wo man gänzlich ohne Events, Callbacks und Listeners auskommt und welche sich hervorragend für alle Arten von Echtzeitanwendungen eignet. Ich habe derartiges in den Weihnachtsfeiertagen prototypisch implementiert und es ist traumhaft. Im Endeffekt hast du im gesamten Code je genau einen Methodenaufruf pro angezeigtem UI-Control, dieser deckt alles ab von Anzeige über Interaktion bis Aktualisierung des zugehörigen Wertes. Der Code-, Komplexitäts- und Speicheroverhead ist effektiv gleich Null. Ich habe hier bereits kurz angesprochen, wie problematisch und vor allem unnötig aufwendig die Anbindung derart großer heterogener Datenmengen, wie man sie in Spielen findet, an eine Editor-UI im Rahmen klassischer Model-View-Controller-Architekturen ist (es spricht denke ich für sich, dass mehrere große Teams in der Spieleindustrie eine Zweitversion ihrer Engine in C# angefertigt haben, nur um dort den Performanceansprüchen zu entfliehen und über Reflection bzw. Auto-Binding eine fette .NET-UI dranhängen zu können, deren Software-Komplexität sich sonst verbieten würde). Leider habe ich gerade kaum Zeit und muss mit dem Verweis auf folgenden Artikel vorlieb nehmen: Immediate Mode Model/View/Controller.

Zu deinem Pseudocode möchte ich nur noch anregen, eine Hilfs-Template-Überladung für createDelegate anzufertigen, die in einer variadischen Parameterliste abwechselnd Zeiger auf die std::function-Members der Delegate-Struktur und eine Funktion für den entsprechenden Funktionseintrag entgegennimmt:

Code: Alles auswählen

button->setDelegate( createDelegate(&ButtonDelegate::Clicked, [&](...) { mein Click-Handler }, ... /* evtl weitere Member-Pointer mit weiteren Event-Handlers */) )
Denn Rückgabetyp von createDelegate kannst du dann aus dem T::-Teil eines übergebenen Member-Zeigers deduzieren und du hast eine ähnlich komfortable/knappe Syntax wie in C#.
Zuletzt geändert von CodingCat am 14.01.2014, 18:28, insgesamt 1-mal geändert.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: Event-Container in C++ 11

Beitrag von Niki »

CodingCat hat geschrieben:... juckt es mich sehr in den Fingern, mehr zu zustandsloser UI zu schreiben
Danke für die Info. Ich werde mir das mal für den nächsten Gang in die Badewanne ausdrucken. Zu diesem Zeitpunkt werde ich aber bei einer zustandsbehafteten UI bleiben, weil ich auch so schon zu viel Zeit mit Research verplempere. Das Problem ist, dass es immer irgendwas besseres gibt, und ich versuchen muss mich zu zwingen endlich mal weiter zu machen :-D
CodingCat hat geschrieben:Zu deinem Pseudocode möchte ich nur noch anregen, eine Hilfs-Template-Überladung für createDelegate anzufertigen, die in einer variadischen Parameterliste abwechselnd Zeiger auf die std::function-Members der Delegate-Struktur und eine Funktion für den entsprechenden Funktionseintrag entgegennimmt:
Hah! Prima! Das gefällt mir. Noch vor einer Stunde habe ich mich gefragt ob ein solches Feature wohl einfach zu unterstützen ist, habe aber noch nicht tiefer drüber weiter nachgedacht. Ich werde das so umsetzen. Aber erst wenn ich mit der verflixten Layout-Engine fertig bin, was ein ziemlicher Aufwand ist, wenn man's richtig hinkriegen will... aber der Aufwand zahlt sich für mich aus.
Antworten