Ich habe eine Signal
-Klasse in meiner Anwendung, die Klassen mit einer Option zur Verfügung stellt, um Ereignisse (wie in .NET) verfügbar zu machen.
Die Klasse funktioniert und alles ist gut.
Gestern habe ich diese SO-Frage (und ihre Antwort) gesehen und mich mit std::forward
vertraut gemacht.
Ich entschied mich, es in meinem Code zu verwenden, also änderte ich jedes std::function<void(Args...)>
in std::function<void(Args&&...)>
und in der Raise-Funktion ( operator()
) benutzte ich die gleiche Logik wie oben im Link, also jetzt die Funktion nimmt Args&&...args
und der Callback verwendet std::forward<Args>(args)...
Hier ist eine vereinfachte Version meiner Signal-Klasse (mit einigen Änderungen, um es ein gutes Beispiel zu machen):
%Vor% Das Problem, das ich sehe, ist mit Klassen (oder main
in diesem Beispiel), die versuchen, Ereignisse mit Zeigern auszulösen, und ich bin mir nicht sicher, wie ich das lösen soll.
Mein Hauptziel ist es, der Signal-Klasse eine allgemeine API zu geben und wenn die Entwickler Signal<int*>
verwenden wollten, möchte ich nicht, dass sie mit std::move
erhöht.
Was mache ich hier falsch?
T&&
ist nur eine universelle Referenz, wenn T
ein nicht-cv-qualifizierter Funktionsschablonenparameter ist. In Ihrem Anrufoperator:
Args
ist kein Vorlagenparameter der Funktion, sondern ein Vorlagenparameter der Klasse. Für Signal<int*>
nimmt diese operator()
also eine rvalue-Referenz auf int*
. Da six
ein Lvalue ist, schlägt das fehl.
Sie möchten die korrekten Referenzqualifikationen für Signal
bereitstellen. Wie so:
Beachten Sie, dass die Weiterleitung von Args...
ohnehin fraglich ist. Was wäre, wenn Sie zwei Abonnenten hätten? Sobald Sie die Argumente einmal weitergeleitet haben, können Sie sie nicht mehr ein zweites Mal verwenden.
Das oben genannte wird dazu führen, dass Signal<int*>
das tut, was Sie erwarten. Das operator()
wird nur ein int*
annehmen, an das Sie entweder einen Lvalue oder einen Rvalue übergeben können.
Barrys Antwort ist richtig, aber vielleicht nicht so klar erklärt, wie es sein könnte.
&&
wird nur dann als eine Weiterleitungs- (oder "universelle") Referenz behandelt, wenn der Vorlagenparameterabzug auftritt . Aber hier tritt kein Abzug auf:
Wenn Vorlagenklassen instanziiert werden, werden sie im Wesentlichen in normale Klassen umgewandelt, die zufällig vom Compiler geschrieben wurden. Siehe diese Antwort für ein Beispiel, wie dies aussehen könnte, wenn es in C ++ - Code geschrieben wird.
In C ++ 14 gibt es no Möglichkeit, die Template-Parameter-Ableitung von Klassenvorlagen ohne eine Hilfefunktion (kein Konstruktor) auszulösen:
%Vor% .... Beachten Sie jedoch, dass dies in Ihrem Fall keinen Sinn ergibt: Sie möchten die Typen Ihrer Argumente nicht ableiten , wenn Sie das Signal
erstellen, das Sie möchten spezifizieren sie im Voraus.
(Beachten Sie, dass in C ++ 17 für Template Argumentabzug von Klassenvorlagen . Ich gehe davon aus, dass es möglich ist forward
Template-Klasse Argumente, obwohl es mir nicht sofort klar ist, was die Auswirkungen solcher tun eine Sache wäre.)
Was Sie zulassen möchten, ist, dass Argumente zur Anrufzeit weitergeleitet werden. Das ist eigentlich ziemlich einfach:
%Vor%... Aber in Ihrem Fall macht das nicht ganz Sinn, wie in Barrys Antwort erwähnt. Sie möchten nicht Argumente weiterleiten, wenn Sie mehrere Rückrufe haben, um zu verhindern, dass sie verschoben und wiederverwendet werden.
Es ist möglich, dies zu umgehen, indem Sie die Größe von m_subscribers
prüfen und nur den forward
ing-Code verwenden, wenn es 1
ist, und die Argumente einfach unverändert weitergeben. Dies kann jedoch zu einem verwirrenden Verhalten führen, da die Art und Weise, wie Callbacks aufgerufen werden, im Allgemeinen nicht vom Zustand Ihres Signal
-Objekts abhängt. So könnten Sie vielleicht eine separate Klasse, SingletonSignal
, für Callbacks schreiben, die müssen mit forward
ed-Argumenten aufgerufen werden müssen (zB für den Fall, dass ein Callback Eigentümerschaft übertragen will) ein nicht kopierbares Objekt wie unique_ptr
).