Vor Multi-Calls schützen



  • Eisflamme schrieb:

    Und überhaupt... so viel Boilerplate-Code 😞 das muss doch eleganter gehen.

    Edit2:
    Na ja, aber wenn wir konsequent überall solche Flags einsetzen, dann reicht es ein einziges Signal zu haben... alles andere nützt ja auch nichts.

    Was passt dir denn an dem von mir geposteten Ansatz nicht?

    Da hast du sowohl ein einziges signal als auch keine flags (weil nur die base-class den flag Mechanismus implementiert). Recht viel kürzer/einfacher wirds wohl nicht gehen aber warum auch?



  • hf2:
    Leider habe ich die Art von Eingriffsmöglichkeit nicht, da QT dataChanged z.B. bereits definiert. Andere QT-Models ebenso. Insofern ist das wohl eine notwendige Voraussetzung... sonst sieht dein Konzept ganz nice aus.

    Mechanics:
    Okay, mein Beispielcode würde ja jetzt die Abhängigkeit von superModel voraussetzen, habe ja ein Codebeispiel gepostet, würdest du das auch so umsetzen?

    Was mir an deinem Konzept gefällt: Mit einem Design von SuperModel - Model - UI kommuniziert links nach rechts grundsätzlich über signals, rechts nach links grundsätzlich über direkte Calls. Das ist intuitiv.

    Wieso denkst du denn, dass die Signals zu Problemen führen werden? Und wie löst du den Boilerplate-Code, den ich jetzt zwangsläufig habe?



  • Eisflamme schrieb:

    hf2:
    Leider habe ich die Art von Eingriffsmöglichkeit nicht, da QT dataChanged z.B. bereits definiert. Andere QT-Models ebenso. Insofern ist das wohl eine notwendige Voraussetzung... sonst sieht dein Konzept ganz nice aus.

    Der Vorschlag ist doch völlig unabhängig von dataChanged? Es geht ja nur um den setter, nämlich setData und den kann man ja beliebig implementieren? Z.b in QAbstractItemModel::setData.

    Du musst das signal dann halt noch richtig verlinken, aber das musst du ja sowieso dewegen versteh ich dein argument nicht 😕



  • Stimmt.

    signal ist halt ein QT-Signal und ich weiß nicht, ob das gut mit Vererbung funktioniert. Überhaupt bin ich kein großer Form von Vererbung bei solchen Models... sieht erstmal so aus, als wäre es eine prima Lösung, muss ich mit QT mal testen, ob das alles brav funktioniert. 🙂

    Könnte man übrigens auch mit CRTP statt Polymorphie lösen, oder?



  • Ich sehe das auch so wie hf2, sein Vorschlag müsste auch mit Qt funktionieren.

    Eisflamme schrieb:

    Wieso denkst du denn, dass die Signals zu Problemen führen werden?

    Weil du im Signalhandler evtl. wieder das gleiche Signal auslösen müsstest: onDataChanged -> dataChanged. Das führt ja eben zum Problem. Wenn die Models nur ein setData haben und dadrin die dataChanged Signale auslösen, aber selber nicht auf die Signale reagieren, sind die zyklischen Abhängigkeiten weg.

    Eisflamme schrieb:

    Und wie löst du den Boilerplate-Code, den ich jetzt zwangsläufig habe?

    Mir ist das meistens egal. Ich hab glaub schon alles mögliche mal gemacht, z.B. int recursionCounter (++/--), BoolGuard Klasse oder so, blockSignals (auch in ein RAII struct verpackt), Signale connecten/disconnecten...

    Ich hab aber keinen Code, wo das wichtig oder tatsächlich entscheidend für die Architektur wäre. Ich hab die Models bisher auch nicht auf die Weise verbunden und kaum mal in mehreren Views benutzt. Ich habe meist andere Probleme. Sagen wir mal, ich hab so eine Art Visualisierung für irgendwelche Informationen, die aus völlig unterschiedlichen Datenquellen kommen und von zwanzig anderen Kollegen geschrieben werden. Und nachdem wir jetzt (bzw. schon lang) Qt benutzten und relativ komplexe Oberflächen relativ einfach bauen können, sind in den letzten Jahren sehr viele Anforderungen und Spielereien dazugekommen. Das ganze hat aber relativ wenig mit GUI Programmierung zu tun. Es werden halt sehr viele Informationen, die nie vereinheitlicht wurden in die GUI reingeschmießen und es muss alles perfekt passen. z.B. kann man die Teile aus der GUI dann an zig andere Objekte draggen, und je nachdem, was für Infos drin sind, muss irgendwas unterschiedliches passieren, die Größen, Spalten, Beschriftungen usw. kann sich beliebig dynamisch ändern, es muss alles superperformant sein etc. Ich versuch das alles möglichst unabhängig von der Qt zu lösen und da sind mir solche Kleinigkeit, wie man am schönsten den Boilerplate beim Aufrufen von Signalen verhindert ziemlich egal, da tipp ich das runter, was mir in der ersten halben Sekunden in den Sinn kommt.



  • Aber wenn ein Supermodel dem Model via Signal (wär ja mit deinem Vorschlag die Konsequenz) ne Änderung mitteilt, muss das doch wiederum signalisieren, dass sich was geändert hat, damit das auch die views darstellen. Somit löst ein Eventhandler ja eben wieder Events aus.

    Gut, zyklisch ists nicht mehr, nur einmal zurück (view -> Model -> view). Mit Flags oder hf2s Lösung lös ich ja aber auch das Zyklische, somit spricht da dann doch auch nichts mehr dagegen, dass Model das Supermodel via Signals alarmiert. Oder überseh ich was?



  • Ja, onDataChanged löst wieder ein dataChanged aus, aber es geht jeweils nur in eine Richtung.
    Klar, du hättest unnötige Round Trips. Die könnten du evtl. abfangen, in dem du prüfst, ob die Daten sich tatsächlich geändert haben, wo das halt funktioniert.
    Ich hab nichts gegen die Lösung von hf2. Du kannst es schon bei den Signalen belassen, wenn du willst. Ich hab nur gemeint, dass du mal drüber nachdenken solltest, ob du das wirklich so willst. Obs noch irgendwelche Probleme geben wird, weiß ich nicht, ich könnts mir aber vorstellen.



  • Hi,

    du meinst, es könnte Probleme dabei geben, die man evtl. nicht hätte, wenn Model Methoden von SuperModel aufruft statt Signale zu nutzen?

    Versteh ich!

    Überdenke ich mal.

    Die ultimativ saubere Lösung sehe ich bei der ganzen Konstellation dennoch nicht. Ich glaube, es gibt aber auch einfach keine.

    ShadeOfMine scheint das Problem ja nicht zu haben... warum, verstehe ich nicht. Ist mein Fall so speziell? Viele Repräsentationen/Anfassmöglichkeiten für dieselbe Datenbasis, das erscheint mir eher ein Alltagsproblem zu sein.



  • Eisflamme schrieb:

    Die ultimativ saubere Lösung sehe ich bei der ganzen Konstellation dennoch nicht. Ich glaube, es gibt aber auch einfach keine.

    Hatte vor kurzem ein ähnliches Problem und hab es mit dem Vergleich (data != m_data) gelöst, hatte damit noch nie Probleme.

    Es wurden ja schon einige Möglichkeiten genannt. Jede hat Vor- und Nachteile, wie meistens halt, leider ist nicht klar was du unter "ultimativ sauber" vestehst bzw scheint mir das relativ subjektiv zu sein was du dir vorstellst.

    Die ganzen Qt-Modells benuzten doch eh massig (Mehrfach)-Vererbung, versteh deswegen nicht so ganz warum du das vermeiden willst. Funktionieren tut es auf jeden Fall auch mit Qt (slots dürfen ja z.B. explizit auch virtual sein).

    Das Gleiche ohne Vererbung:

    #include <iostream>
    #include <string>
    #include <boost\signals2\signal.hpp>
    
    using namespace boost::signals2;
    
    template <typename Data, typename Owner>
    struct DataGuard
    {
    	bool syncing = false;
    
    	void setDataGuarded(Data data, Owner *owner)
    	{
    		if (!syncing) {
    			syncing = true;
    			owner->setData(data);
    			syncing = false;
    		}
    	}
    };
    
    struct DataModel {
    
    	int value;
    	signal<void(int)> dataChanged;
    	DataGuard<int, DataModel> dataGuard;
    
    	void setData(int newValue) {
    		value = newValue;
    		dataChanged(newValue);
    	}
    };
    
    struct DataStringModel {
    
    	std::string valueFormated;
    	signal<void(int)> dataChanged;
    	DataGuard<int, DataStringModel> dataGuard;
    
    	void setData(int newValue) {
    		valueFormated = "str(" + std::to_string(newValue) + ")";
    		dataChanged(newValue);
    	}
    };
    
    int main() {
    
    	DataModel dm;
    	DataStringModel dsm;
    
    	dm.dataChanged.connect([&dsm](int i) { dsm.dataGuard.setDataGuarded(i, &dsm); });
    	dsm.dataChanged.connect([&dm](int i) { dm.dataGuard.setDataGuarded(i, &dm); });
    
    	dm.setData(3);
    	std::cout << "dm: " << dm.value << std::endl;
    	std::cout << "dsm: " << dsm.valueFormated << std::endl;
    
    	dsm.setData(5);
    	std::cout << "dm: " << dm.value << std::endl;
    	std::cout << "dsm: " << dsm.valueFormated << std::endl;
    }
    

    Aber ob das jetzt so viel besser ist sei mal dahingestellt?



  • Hi,

    Änderungen feststellen kann halt kostspielig sein, wenn Daten dafür aufbereitet werden müssen. Sonst wäre das die einfache Variante. Für Slider und Spinboxen geht das, für ganze Tabellen eben nicht.

    Ultimativ sauber könnte heißen, dass das Konzept eingängig ist und ich nach 3-4 Wochen nach kurzem Anschauen und Wieder-in-den-Kopf-rufen des Konzepts das sofort verstehe, für neue Models anwenden kann und es sich gut in das aktuelle Design eingliedert.

    Komisch benannte Signale (dataChanged, dataModified, was hieß jetzt noch gleich was?) fallen da raus. Gut benennen ist auch echt schwierig, fand da keine Lösung. Flags sind "ok". Und der Dataguard gefällt mir immer besser. 🙂 Tendiere jetzt auch dazu den (in welcher Form, weiß ich noch nicht) zu nehmen. Da hab ich den eigentlichen Code nicht mit der Problematik verschändelt, also genau, was ich möchte.

    Ich würde es, glaube ich, aber noch etwas generischer haben wollen:

    #include <iostream>
    using namespace std;
    
    struct CallOnceProxy
    {
    	bool executing = false;
    
    	template<typename T>
    	void operator()(T func)
    	{
    		if(!executing)
    		{
    			executing = true;
    			func();
    			executing = false;
    		}
    	}
    };
    
    void bar();
    
    CallOnceProxy call1;
    CallOnceProxy call2;
    
    void foo()
    {
    	call1([&]() {
    		cout << "foo()\n";
    		bar();
    	});
    }
    
    void bar()
    {
    	call2([&](){
    	cout << "bar()\n";
    	foo();
    	});
    }
    
    int main()
    {
    	std::cout << "initial foo: \n";
    	foo();
    
    	std::cout << "\ninitial bar: \n";
    	bar();
    
    	return 0;
    }
    

    Polymorphie sparen wir uns an Stellen, an der wir sie nicht brauchen. Und mit der Syntax kann ich mich auch anfreunden (mit Sicherheit Geschmackssache, stimmt). Seht ihr irgendwelche direkten Nachteile?

    Edit: Wenn die Überprüfung, ob eine Änderung geschehen ist, günstiger ist, dann ist das natürlich die bessere Variante. Die beiden zusammen sind vielleicht gar nicht schlecht.

    Dann werde ich das changed-Signal wieder auf eins reduzieren. Das finde ich sowieso intuitiver... ob etwas geändert wurde, ist ja schließlich logisch gesehen ein Event, also ist auch ein Signal dafür gut.


Anmelden zum Antworten