2016-08-02 17 views
8

Ich habe eine Signal Klasse in meiner Anwendung, die Klassen mit einer Option zur Verfügung stellt, Ereignisse (wie in. NET).C++ 11 Std :: einen Zeiger weiterleiten

Die Klasse funktioniert und alles ist gut.

Gestern sah ich this SO question (and its answer) und wurde vertraut gemacht mit std::forward.

ich es in meinem Code zu verwenden, um zu versuchen entschieden, damit ich jeden std::function<void(Args...)> zu std::function<void(Args&&...)> und in der Erhöhung Funktion (die operator()) geändert habe ich die gleiche Logik, die ich in dem obigen Link sah so jetzt die Funktion nimmt Args&&...args und den Rückruf std::forward<Args>(args)... verwendet

Hier ist eine vereinfachte Version meiner Signalklasse (mit einigen Änderungen es ein gutes Beispiel zu machen):

template<typename... Args> class Signal 
{ 
public: 
    int operator+=(const std::function<void(Args&&...)>& func) { 
     int token = getNewToken(); 
     m_subscribers.emplace(token, func); 
     return token; 
    } 

    void operator()(Args&&... args) { 
     for (auto it : m_subscribers) { 
      it.second(std::forward<Args>(args)...); 
     } 
    } 

private: 
    std::map<int, std::function<void(Args&&...)>> m_subscribers; 
}; 

int main() { 
    int* six = new int(6); 
    int seven = 7; 

    Signal<int*> e1; 
    e1 += [](int* x) { std::cout << *x; }; 

    Signal<int> e2; 
    e2 += [](int x) { std::cout << x; }; 

    e1(&seven); 
    e2(6); 

    e1(six); //Error C2664 'void Signal<int *>::operator()(int *&&)': 
       // cannot convert argument 1 from 'int *' to 'int *&&' 
    e1(std::move(six)); //This is a workaround   
    return 0; 
} 

Die Frage, die ich zu sehen bin mit Klassen (oder main in diesem Beispiel) ist, dass Versuchen Sie, Ereignisse mit pointe auszulösen rs und ich bin mir nicht sicher, wie ich das lösen soll.

Mein Hauptziel ist es, die Signal-Klasse eine allgemeine API zu haben, und wenn die Entwickler Signal<int*> verwenden, möchte ich nicht, dass er mit std::move erhöht.

Was mache ich hier falsch?

Antwort

11

T&& ist nur eine universelle Referenz, wenn T ein nicht cv-qualifizierter Funktionsschablonenparameter ist. In Ihrem Call-Betreiber:

void operator()(Args&&... args) { 

Args ist kein Template-Parameter der Funktion, es ist ein Template-Parameter der Klasse. Also für Signal<int*>, diese operator() nimmt einen rvalue Verweis auf int*. Da six ein Lvalue ist, schlägt das fehl.

Sie möchten die richtigen Referenzqualifikationen zu Signal bereitstellen. Wie so:

template<typename... Args> 
class Signal 
{ 
    using F = std::function<void(Args...)>; // NB: Just Args... 
public: 
    int operator+=(F func) { 
     int token = getNewToken(); 
     m_subscribers.emplace(token, std::move(func)); 
     return token; 
    } 

    void operator()(Args... args) {  // NB: just Args... 
     for (auto& it : m_subscribers) { // NB: auto& 
      it.second(args...); 
     } 
    } 

private: 
    std::map<int, F> m_subscribers; 
}; 

Beachten Sie, dass Args... Spedition ist ohnehin fraglich. 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 obige wird Signal<int*> tun, was Sie erwarten. Die operator() wird nur eine int* nehmen, an die Sie entweder einen Lvalue oder einen Rvalue übergeben können.

+2

@WernerErasmus Ja. Wenn Sie mehrere Abonnenten haben, können Sie dieselben Argumente nicht mehrfach weiterleiten. – Barry

+0

Danke Barry, außer dem 'using F' Teil, das ist der Code den ich bereits benutze. Ihre Antwort ist also gewissermaßen "Sie können und sollten in diesem Fall nicht std :: forward verwenden"? – ZivS

+0

Ich bin mehr daran interessiert, wie und ob ich std :: forward mit allen Typen verwenden kann. Ich habe das Gefühl, dass ich mehr darüber lesen sollte, da der erste Absatz Ihrer Antwort ein wenig nuklear für mich war ... – ZivS

1

Barrys Antwort ist korrekt, aber vielleicht nicht so klar erklärt, wie es sein könnte.

&& ist nur eine spezielle Behandlung als Forwarding (oder "universal") Referenz gegeben, wenn Vorlage Parameter Abzug tritt. Aber es gibt keinen Abzug hier vorkommen:

Signal<int*> e1; // `Args...` is explicitly `int*` 
... 
e1(six);   // `Args...` *has already been specified* 

Wenn Template-Klassen instanziiert werden, werden sie im Wesentlichen in normale Klassen umgewandelt, die nur durch den Compiler geschrieben werden passieren. Ein Beispiel dafür, wie dies aussehen könnte, wenn es in C++ - Code geschrieben wird, finden Sie unter this answer.

In C++ 14, gibt es keinen Weg Template-Parameter Abzug der Klassen-Templates ohne Hilfs Funktion (kein Konstruktor) auslösen:

template <typename Args...> 
Signal<Args...> make_signal(Args&&...) { return Signal<Args...>; } 

.... Aber beachten Das macht in Ihrem Fall keinen Sinn: Sie wollen nicht die die Arten Ihrer Argumente ableiten, wenn Sie die Signal erstellen, möchten Sie spezifizieren Sie sie im Voraus.

(Beachten Sie, dass in ++ 17 C gibt wird Unterstützung sein für template argument deduction of class templates. Ich nehme an, dies bedeutet, dass es möglich sein wird, forward Template-Klasse Argumente, obwohl es mir nicht sofort klar ist, was die Auswirkungen tun so eine Sache wäre.)

Was Sie zulassen möchten, ist für Argumente weitergeleitet werden zum Zeitpunkt des Anrufs. Dies ist eigentlich recht einfach:

template<typename... Args> class Signal 
{ 
public: 
    // .... skipping some code... 

    template <typename... CallArgs> 
    void operator()(CallArgs&&... args) { 
     callback(std::forward<CallArgs>(args)...); 
    } 
}; 

.... Aber noch einmal, in Ihrem Fall, dass dies nicht ganz sinnvoll, da in Barrys Antwort zur Kenntnis genommen. Sie möchten nicht, dass Argumente weiterleitet, wenn Sie mehrere Rückrufe haben, um zu verhindern, dass sie verschoben und wiederverwendet werden.

Es ist möglich, dies zu umgehen, indem die Größe von m_subscribers Überprüfung und nur mit dem forward ing-Code, wenn es 1 ist, und vorbei nur die Argumente anders als sie ist. 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önnte man vielleicht eine eigene Klasse schreiben, SingletonSignal, für Rückrufe dass muss mit forward ed Argumenten geltend gemacht werden (zum Beispiel bei einem Rückruf zu Transfer Eigentum will eines nicht kopierbaren Objekt wie unique_ptr).