SimpleSig - minimalistic signal/slots lib for C++

10/20/2013 18:36 Master674b#16
Quote:
Originally Posted by Tasiro View Post
Warum so kompliziert void etc. ausschalten? Es reicht, ProxyRet<void> zu spezialisieren und ProxyRet<const void> etc. davon ableiten zu lassen.


std::move ist in utility definiert. std::enable_if in type_traits.


Compare (InputIterator first, InputIterator last): Die Benennung last ist irreführend. last wird niemals dereferenziert.


Minimum und Maximum sind einfacher mit std::min_element und std::max_element zu implementieren, Signal::size mit std::count_if.


ProxyRet <R> operator () (const Args &... args) const: Was ist mit "perfect forwarding"?


Warum muss ich die "custom-compare Funktionen" in eine Klasse packen, Compare nennen und zu Templatefunktionen machen? Warum nicht das Konzept der Funktionsobjekte wie in der Standardbibliothek unterstützen?


_generic_invoke(const FuncT& function): Wie wäre es mit remove_if?
Es müsste so gehen, ich habe es nicht getestet:
Code:
template <typename FuncT>
void _generic_invoke (const FuncT & function) const {
	Detail::CallLock g (m_callLock);
	m_connections.remove_if ([& function] (const std::shared_ptr <Detail::ConnectionBase <signature_t>> & connection) {
		if (! connection->connected ())
			return true;
		function (connection->connectedSlot);
		return false;
	});
}
mutable und const heißen beide "ist thread-sicher", oder sollten es. Auch ein konstantes Objekt (oder const T&) braucht ein veränderliches Objekt der Klasse std::mutex.
1. Jo, wäre auch eine Möglichkeit, wohl grade einfach nicht daran gedacht.
2. Ups, type_traits vergessen.
3. Vielleicht, nennt boost und stl aber genauso.
4. Stimmt, die fordern nur noch nen algorithm include auf den ich so auch verzichten kann.
5. Perfect forwarding geht hier nicht. Es sind ja mehrere Funktionen, die die Argumente brauchen. Würde es bei der 1. Funktion in einem move resultieren, hätten die restlichen ein leeres Ergebnis, da das Argument gemoved wurde.
6. kA wie meinst du denn? mit ner Predicate Funktion als invoke Argument?
7. Nicht gesehen, dass std::forward_list eine remove_if Funktion anbietet.
10/20/2013 19:36 MrSm!th#17
Quote:
mutable und const heißen beide "ist thread-sicher", oder sollten es. Auch ein konstantes Objekt (oder const T&) braucht ein veränderliches Objekt der Klasse std::mutex.
Achso, mir ist entgangen, dass es sich nur um einzelne Member und nicht das ganze Objekt dreht.

Nochmal zu std::function, weil ich mir eigentlich sicher war, dass std::function zu dem Zweck existiert, der Arbeit mit Lambdas, Functors und normalen Funktionspointern eine gemeinsame Schnittstelle anzubieten. Also müsste doch ein Parameter vom Typ std::function auch alle 3 fassen können, oder habe ich da etwas falsch verstanden?
10/20/2013 21:27 Tasiro#18
Quote:
Originally Posted by Master674b View Post
6. kA wie meinst du denn? mit ner Predicate Funktion als invoke Argument?
Es wäre schön, wenn ich ein Lambda nutzen könnte.
static Compare ist nicht so schön wie operator () (iter begin, iter end) const.

Quote:
Originally Posted by MrSm!th View Post
Nochmal zu std::function, weil ich mir eigentlich sicher war, dass std::function zu dem Zweck existiert, der Arbeit mit Lambdas, Functors und normalen Funktionspointern eine gemeinsame Schnittstelle anzubieten. Also müsste doch ein Parameter vom Typ std::function auch alle 3 fassen können, oder habe ich da etwas falsch verstanden?
std::function akzeptiert so ziemlich alles als Argument, nur ist es i. Allg. langsamer. Hier nicht, da der Typ der Slots nicht bekannt ist.
10/20/2013 21:58 Master674b#19
Quote:
Originally Posted by Tasiro View Post
Es wäre schön, wenn ich ein Lambda nutzen könnte.
static Compare ist nicht so schön wie operator () (iter begin, iter end) const.

std::function akzeptiert so ziemlich alles als Argument, nur ist es i. Allg. langsamer. Hier nicht, da der Typ der Slots nicht bekannt ist.
Ist aber ein Problem, wenn es ein Template ist, dann müsstest du den so aufrufen:

Code:
CompareF::operator()<T>(...);
das ist dann nicht mehr so schön. Da müsste dann der Funktor als template definiert werden.
10/20/2013 22:21 Tasiro#20
Quote:
Originally Posted by Master674b View Post
Ist aber ein Problem, wenn es ein Template ist, dann müsstest du den so aufrufen:

Code:
CompareF::operator()<T>(...);
das ist dann nicht mehr so schön. Da müsste dann der Funktor als template definiert werden.
Ja, wie std::less. Als Operation übergebe ich etwa std::less <int> ().
Auf deine Signale bezogen, wäre das dann z. B.:
Code:
std::cout << Signal <int (int, int, int)> ({slot1, slot2}).invoke ({1, 2, 3}, [] (std::initializer_list <int> results) {
    return std::max_element (results.begin (), results.end ());
}):
10/21/2013 13:35 Master674b#21
Ok, überleg ich mir.
10/21/2013 23:21 Tasiro#22
Ich sehe gerade - es sind std::unique_ptr, std::shared_ptr und std::weak_ptr dabei, fehlt nur noch std::auto_ptr.
Könntest du mir bitte erklären, wozu du std::shared_ptr brauchst (und nicht std::unique_ptr und T*, oder std::shared_ptr<const T>)? Es wäre schön, ein Beispiel dafür zu haben.

Es fehlt vielleicht noch der ein oder andere Kommentar hier und da, etwa bei ProxyRet<const void>.
10/21/2013 23:50 Master674b#23
Quote:
Originally Posted by Tasiro View Post
Ich sehe gerade - es sind std::unique_ptr, std::shared_ptr und std::weak_ptr dabei, fehlt nur noch std::auto_ptr.
Könntest du mir bitte erklären, wozu du std::shared_ptr brauchst (und nicht std::unique_ptr und T*, oder std::shared_ptr<const T>)? Es wäre schön, ein Beispiel dafür zu haben.

Es fehlt vielleicht noch der ein oder andere Kommentar hier und da, etwa bei ProxyRet<const void>.
Connection Objekte können geshared werden. Deswegen, geht gar nicht mit nem unique_ptr.
10/22/2013 00:56 Tasiro#24
Quote:
Originally Posted by Master674b View Post
Connection Objekte können geshared werden.
Wie? Durch Kopieren des Signals? Wenn ich dann eine Kopie erstelle und danach die vorher dem Orginal hinzugefügte Verbindung trenne, wird doch aber auch in der (vielleicht genau deswegen erstellten) Kopie das Signal getrennt... Soll das so?

Beispiel:
Code:
int f () { return 1; }
int g () { return 2; }

int main () {
	SimpleSig::Signal <int ()> signal1;
	auto connection1 (signal1.connect (f));
	auto connection2 (signal1.connect (g));
	std::cout << * signal1.invoke <SimpleSig::Maximum> () << '\n';
	auto signal2 (signal1);
	std::cout << * signal1.invoke <SimpleSig::Maximum> () << " - " << * signal2.invoke <SimpleSig::Maximum> () << '\n';
	connection2.disconnect ();
	std::cout << * signal1.invoke <SimpleSig::Maximum> () << " - " << * signal2.invoke <SimpleSig::Maximum> () << '\n';
	std::cin.get ();
}

//Ausgabe:
// 2
// 2 - 2
// 1 - 1
10/22/2013 14:39 Master674b#25
Quote:
Originally Posted by Tasiro View Post
Wie? Durch Kopieren des Signals? Wenn ich dann eine Kopie erstelle und danach die vorher dem Orginal hinzugefügte Verbindung trenne, wird doch aber auch in der (vielleicht genau deswegen erstellten) Kopie das Signal getrennt... Soll das so?

Beispiel:
Code:
int f () { return 1; }
int g () { return 2; }

int main () {
	SimpleSig::Signal <int ()> signal1;
	auto connection1 (signal1.connect (f));
	auto connection2 (signal1.connect (g));
	std::cout << * signal1.invoke <SimpleSig::Maximum> () << '\n';
	auto signal2 (signal1);
	std::cout << * signal1.invoke <SimpleSig::Maximum> () << " - " << * signal2.invoke <SimpleSig::Maximum> () << '\n';
	connection2.disconnect ();
	std::cout << * signal1.invoke <SimpleSig::Maximum> () << " - " << * signal2.invoke <SimpleSig::Maximum> () << '\n';
	std::cin.get ();
}

//Ausgabe:
// 2
// 2 - 2
// 1 - 1
Jo das soll so sein. Findest du das negativ? Du kannst die Connection-Objekte selbst ja auch kopieren bzw. davon ne ScopedConnection erstellen, die muss dann ja zwangsweise auch auf das selbe ConnectionBase Objekt zeigen.

Das mit dem Kopieren des Signals könnte man auch ändern, wenn gewünscht. Dazu müsste man einfach im Kopierkonstruktor ne neue Kopie der ConnectionBase machen, statt den shared_ptr zu kopieren. Dann kannst du die bereits verbundenen Signale aus dem kopierten Signal nur noch über clear disconnecten. Bin mir grad nicht sicher, ob ich das positiv oder negativ finde.
10/22/2013 15:52 Tasiro#26
Quote:
Originally Posted by Master674b View Post
Jo das soll so sein. Findest du das negativ? Du kannst die Connection-Objekte selbst ja auch kopieren bzw. davon ne ScopedConnection erstellen, die muss dann ja zwangsweise auch auf das selbe ConnectionBase Objekt zeigen.
Das ist kontraintuitiv. Wenn ich etwas kopiere, dann nehme ich an, dass die Kopie prinzipiell unabhängig ist und eine nachfolgende Änderung an Elementen des Orginals die Kopie nicht beeinflusst. Du kannst zwar intern intelligente Zeiger nutzen, soviel du willst, das sollte sich aber nicht auf das erwartete Verhalten auswirken.
Eine Verbindung hat für mich nur ein Signal, nicht unbegrenzt viele. Wenn ich also eine Verbindung trenne, wirkt sich das nicht auf alle möglichen Kopien aus. Dann könnte aber die Verbindung eine Referenz auf das besitzende Signal haben und sich selbst aus dem Signal austragen, intelligente Zeiger wären dann dafür auch nicht mehr notwendig. Wenn du dann bei einer verketteten Liste bleibst, sollten die Verbindungen vielleicht noch einen Iterator besitzen, der auf sie selbst (bzw. direkt davor) in der Liste zeigt. Wenn es eine einfach verkettete Liste ist, muss eine sich trennende/schließende Verbindung außerdem den Iterator der nächsten Verbindung aktualisieren. Wenn du keine Reihenfolge für die Aufrufe garantierst, könntest du auch std::vector nutzen und bei einer in der Mitte getrennten/geschlossenen Verbindung diese durch das letzte Element der Liste ersetzen.
10/22/2013 16:54 Master674b#27
Quote:
Originally Posted by Tasiro View Post
Das ist kontraintuitiv. Wenn ich etwas kopiere, dann nehme ich an, dass die Kopie prinzipiell unabhängig ist und eine nachfolgende Änderung an Elementen des Orginals die Kopie nicht beeinflusst. Du kannst zwar intern intelligente Zeiger nutzen, soviel du willst, das sollte sich aber nicht auf das erwartete Verhalten auswirken.
Eine Verbindung hat für mich nur ein Signal, nicht unbegrenzt viele. Wenn ich also eine Verbindung trenne, wirkt sich das nicht auf alle möglichen Kopien aus. Dann könnte aber die Verbindung eine Referenz auf das besitzende Signal haben und sich selbst aus dem Signal austragen, intelligente Zeiger wären dann dafür auch nicht mehr notwendig. Wenn du dann bei einer verketteten Liste bleibst, sollten die Verbindungen vielleicht noch einen Iterator besitzen, der auf sie selbst (bzw. direkt davor) in der Liste zeigt. Wenn es eine einfach verkettete Liste ist, muss eine sich trennende/schließende Verbindung außerdem den Iterator der nächsten Verbindung aktualisieren. Wenn du keine Reihenfolge für die Aufrufe garantierst, könntest du auch std::vector nutzen und bei einer in der Mitte getrennten/geschlossenen Verbindung diese durch das letzte Element der Liste ersetzen.
Eigentlich wäre der smart pointer dann immer noch notwendig. Ein Signal könnte ja schon zerstört sein.
Um das zu beheben bräuchten die beiden gegenseitig Referenzen aufeinander, das klingt nach nem Designfehler für mich.
10/22/2013 20:45 Tasiro#28
Quote:
Originally Posted by Master674b View Post
Eigentlich wäre der smart pointer dann immer noch notwendig. Ein Signal könnte ja schon zerstört sein.
Den Gültigkeitsbereich nicht beachten kann ich auch so. Davon abgesehen sollten Verbindungen nicht kopierbar sein und nur als Referenzen, Zeiger oder in Wrapper-Objekten verfügbar sein; was passierte denn, wenn ich eine bestehende Verbindung kopierte?

Quote:
Originally Posted by Master674b View Post
Um das zu beheben bräuchten die beiden gegenseitig Referenzen aufeinander, das klingt nach nem Designfehler für mich.
Dann solltest du nicht anbieten, dass Verbindungen sich selbstständig trennen können. Wenn es effizient sein soll, wird das vom Nutzer verwendete Objekt so oder so einen Zeiger auf oder in das Signal haben, auch wenn das dann nicht das vom Signal verwaltete Objekt sein muss. Du könntest etwa (nur ein Beispiel) in dem Signal eine Liste von std::function<T> haben und bei Bedarf ein Verbindungsobjekt erstellen (connection_ref<T>), welches zum einen auf das std::function<T>-Objekt und zum anderen auf das Signal zeigt. Damit hast zu zyklische Zeiger erfolgreich vermieden, kannst aber dafür keine Referenz mehr zurückgeben und musst das Objekt jedes Mal neu erstellen. Außerdem können dann Verbindungen ohne besitzendes Signal nicht existieren. (Wer will der Erste sein, der darlegt, unter welchen Umständen das vielleicht doch möglich wäre?)
10/22/2013 21:21 Master674b#29
Quote:
Originally Posted by Tasiro View Post
Den Gültigkeitsbereich nicht beachten kann ich auch so. Davon abgesehen sollten Verbindungen nicht kopierbar sein und nur als Referenzen, Zeiger oder in Wrapper-Objekten verfügbar sein; was passierte denn, wenn ich eine bestehende Verbindung kopierte?

Dann solltest du nicht anbieten, dass Verbindungen sich selbstständig trennen können. Wenn es effizient sein soll, wird das vom Nutzer verwendete Objekt so oder so einen Zeiger auf oder in das Signal haben, auch wenn das dann nicht das vom Signal verwaltete Objekt sein muss. Du könntest etwa (nur ein Beispiel) in dem Signal eine Liste von std::function<T> haben und bei Bedarf ein Verbindungsobjekt erstellen (connection_ref<T>), welches zum einen auf das std::function<T>-Objekt und zum anderen auf das Signal zeigt. Damit hast zu zyklische Zeiger erfolgreich vermieden, kannst aber dafür keine Referenz mehr zurückgeben und musst das Objekt jedes Mal neu erstellen. Außerdem können dann Verbindungen ohne besitzendes Signal nicht existieren. (Wer will der Erste sein, der darlegt, unter welchen Umständen das vielleicht doch möglich wäre?)
Ich guck mir nachher mal an wie andere Signal libs das regeln. Jedenfalls brauch ich diese Sicherheit hier. Bei mir im Projekt ist das ein LuaSignal, d.h. die Klasse brauche ich, um Lua-Callbacks zu realisieren, und die sollen natürlich so sicher wie nur irgend wie möglich sein. Auch wenn im Script der Gültigkeitsbereich der Verbindungen missachtet wird, darf nichts schief gehen.

Danke dir übrigens für die Mühen und Vorschläge =) Sobald ich meine VS 2013 Lizenz hab guck ich mal ob ich das ganze eventuell noch etwas anpasse oder mehr Optionen bereitstelle.
10/23/2013 00:34 Tasiro#30
Quote:
Originally Posted by Master674b View Post
Ich guck mir nachher mal an wie andere Signal libs das regeln. Jedenfalls brauch ich diese Sicherheit hier. Bei mir im Projekt ist das ein LuaSignal, d.h. die Klasse brauche ich, um Lua-Callbacks zu realisieren, und die sollen natürlich so sicher wie nur irgend wie möglich sein. Auch wenn im Script der Gültigkeitsbereich der Verbindungen missachtet wird, darf nichts schief gehen.
Es ist also für eine Programmiersprache? Das ist dann ein anderes Thema. Ich kenne Lua nicht, wenn es aber schwach typisiert ist und eine automatische Speicherverwaltung sein Eigen nennt, müsste doch eigentlich irgendwo eine Referenz "hängen bleiben" und somit verhindern, dass deine Rückruf-Funktionen verworfen wird. Du müsstest dann doch nur noch testen, ob es wirklich Funktionen ist. Damit ist Rückrufen Genüge getan. Zumindest, wenn Lua die Verbindungen nicht selbst verwalten darf... Aber auch dann unterstützt deine Signal-Bibliothek das momentan nicht hinreichend, dass etwas kaputtgehen könnte - weder durch Lua, noch durch irgendjemanden sonst.
Angenommen, deine Verbindungen haben keinen intelligenten Zeiger oder eine sonstige Sicherungsmethode. Solange du mit deinen Signalen umgehst, wird das aber auch nur dann gefährlich, wenn du Lua die Kontrolle über Verbindungsobjekte übergibst. Diese sollten schließlich nur in dem Kontext eines Signals vorkommen und nicht darüber hinaus am Leben gelassen werden.

Quote:
Originally Posted by Master674b View Post
Danke dir übrigens für die Mühen und Vorschläge =)
Das mache ich gerne, es würde mich freuen, geholfen zu haben.

Quote:
Originally Posted by Master674b View Post
Sobald ich meine VS 2013 Lizenz hab guck ich mal ob ich das ganze eventuell noch etwas anpasse oder mehr Optionen bereitstelle.
Das war der 31.10., oder?