2013-08-21 16 views
97

Lassen Sie uns sagen, dass ich eine Funktion, die eine std::function nimmt: Sollte ich eine Std :: Funktion durch Const-Referenz übergeben?

void callFunction(std::function<void()> x) 
{ 
    x(); 
} 
Sollte ich stattdessen x von const-Verweis übergeben ?:

void callFunction(const std::function<void()>& x) 
{ 
    x(); 
} 

Ist die Antwort auf diese Änderung Frage je nachdem, was die Funktion geht damit? Zum Beispiel, wenn es sich um eine Klassenmemberfunktion oder einen Konstruktor handelt, der std::function in einer Membervariablen speichert oder initialisiert.

+1

Wahrscheinlich nicht. Ich weiß es nicht genau, aber ich würde erwarten, dass 'sizeof (std :: function)' nicht mehr als '2 * sizeof (size_t)' ist, was die kleinste Größe ist, die Sie jemals für eine const-Referenz betrachten würden . –

+9

@Mats: Ich denke nicht, dass die Größe des 'std :: function'-Wrappers so wichtig ist wie die Komplexität des Kopierens. Wenn tiefe Kopien beteiligt sind, könnte es viel teurer sein, als "sizeof" suggeriert. –

+0

Sollten Sie die Funktion "bewegen"? – Yakk

Antwort

26

Wenn Sie sich Sorgen um die Leistung machen und keine virtuelle Elementfunktion definieren, sollten Sie höchstwahrscheinlich überhaupt nicht std::function verwenden.

Wenn Sie den Funktortyp zu einem Vorlagenparameter machen, können Sie eine größere Optimierung als std::function vornehmen, einschließlich der Funktorlogik. Die Auswirkungen dieser Optimierungen überwiegen wahrscheinlich die Bedenken bezüglich der Copy-vs-Indirection, wie man std::function übergibt.

Schneller:

template<typename Functor> 
void callFunction(Functor&& x) 
{ 
    x(); 
} 
+0

Ich bin überhaupt nicht besorgt über die Leistung.Ich dachte nur, Const-Referenzen zu verwenden, wo sie verwendet werden sollten, ist gängige Praxis (Zeichenfolgen und Vektoren kommen mir in den Sinn). –

+9

@Ben: Ich denke, die modernste Hippie-freundliche Art, dies zu implementieren, ist die Verwendung von 'std :: forward (x)();', um die Wertkategorie des Funktors zu erhalten, da es eine "universelle" Referenz ist. In 99% der Fälle wird das jedoch keinen Unterschied machen. – GManNickG

+0

Schön, mir war nicht bewusst, dass dies Lambda-Funktionen zulassen würde. –

19

Wie üblich in C++ 11, vorbei an Wert/reference/const-Referenz hängt davon ab, was Sie mit Ihrem Argumente tun. std::function ist nicht anders.

von Wert Passing können Sie das Argument in eine Variable bewegen (in der Regel eine Membervariable einer Klasse):

struct Foo { 
    Foo(Object o) : m_o(std::move(o)) {} 

    Object m_o; 
}; 

Wenn Sie Ihre Funktion kennen ihr Argument bewegen wird, ist dies die beste Lösung , auf diese Weise die Benutzer steuern können, wie sie Ihre Funktion aufrufen:

Foo f1{Object()};    // move the temporary, followed by a move in the constructor 
Foo f2{some_object};   // copy the object, followed by a move in the constructor 
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor 

ich weiß, dass Sie bereits über die Semantik von (nicht) const Verweise glauben, damit ich nicht den Punkt herumzureiten. Wenn Sie mich brauchen, um weitere Erklärungen dazu hinzuzufügen, fragen Sie einfach und ich werde aktualisieren.

54

Wenn Sie Leistung wünschen, übergeben Sie Wert, wenn Sie es speichern.

Angenommen, Sie haben eine Funktion namens "Führen Sie dies im UI-Thread".

std::future<void> run_in_ui_thread(std::function<void()>) 

, die einen Code in der "ui" Thread läuft, dann signalisiert der future wenn fertig. (Nützlich in UI-Frameworks, wo der UI-Thread ist, wo Sie zu verwirren sollen mit UI-Elemente)

Wir haben zwei Unterschriften wir betrachten:

std::future<void> run_in_ui_thread(std::function<void()>) // (A) 
std::future<void> run_in_ui_thread(std::function<void()> const&) // (B) 

Nun sind wir wahrscheinlich diese wie folgt zu verwenden:

run_in_ui_thread([=]{ 
    // code goes here 
}).wait(); 

, die einen anonymen Verschluss (a lambda) schaffen werden, bauen ein std::function aus ihn heraus, übergeben sie an die run_in_ui_thread Funktion, dann warten, bis es in dem Haupt-Thread ausgeführt zu beenden.

Im Fall (A) wird der std::function direkt aus unserem Lambda konstruiert, der dann innerhalb der run_in_ui_thread verwendet wird. Das Lambda ist move d in die std::function, so dass jeder bewegliche Zustand effizient in es getragen wird.

Im zweiten Fall wird eine temporäre std::function erstellt wird, wird der Lambda-move d hinein, so dass die vorübergehende std::function innerhalb des run_in_ui_thread durch Referenz verwendet wird.

So weit, so gut - die beiden funktionieren identisch. Außer der run_in_ui_thread wird eine Kopie seines Funktionsarguments an den ui-Thread zum Ausführen senden! (es wird zurückkehren, bevor es damit fertig ist, also kann es nicht einfach einen Verweis darauf verwenden). Für den Fall (A) nehmen wir einfach move den std::function in seinen Langzeitspeicher. Im Fall (B) sind wir gezwungen, die std::function zu kopieren.

Dieser Speicher macht das Überschreiten von Wert optimaler. Wenn es irgendeine Möglichkeit gibt, speichern Sie eine Kopie des std::function, pass by value. Ansonsten ist jeder Weg ungefähr gleichwertig: der einzige Nachteil gegenüber dem Wert ist, wenn Sie den gleichen sperrigen std::function nehmen und eine Sub-Methode nach der anderen verwenden. Abgesehen davon wird ein move genauso effizient sein wie ein const&.

Jetzt gibt es einige andere Unterschiede zwischen den beiden, die meistens eingreifen, wenn wir einen dauerhaften Zustand innerhalb der std::function haben.

Angenommen, die std::function speichert ein Objekt mit einer operator() const, aber es hat auch einige mutable Datenmitglieder, die es ändert (wie unhöflich!).

Im std::function<> const& Fall werden die geänderten Datenelemente mutable aus dem Funktionsaufruf propagieren. Im Fall std::function<> werden sie nicht.

Dies ist eine relativ seltsame Ecke Fall.

Sie möchten std::function behandeln, als ob Sie einen anderen, möglicherweise schweren, billig beweglichen Typ hätten. Umzug ist billig, Kopieren kann teuer sein.

+0

Der semantische Vorteil von "pass by value, wenn Sie es speichern", wie Sie sagen, ist, dass vertraglich die Funktion die Adresse des übergebenen Arguments nicht behalten kann. Aber ist es wirklich wahr, dass "ein Zug so effizient sein wird wie ein Const"? Ich sehe immer die Kosten eines Kopiervorgangs plus die Kosten des Verschiebevorgangs. Bei der Übergabe von "const &" sehe ich nur die Kosten für den Kopiervorgang. – ceztko

+1

@ceztko In beiden Fällen (A) und (B) wird die temporäre 'std :: function' aus Lambda erstellt. In (A) wird das Temporäre in das Argument "run_in_ui_thread" eingefügt. In (B) wird ein Verweis auf das temporäre auf "run_in_ui_thread" übergeben. Solange Ihre 'std :: -Funktion' aus Lambdas als Provisorien erstellt wird, gilt diese Klausel. Der vorherige Absatz behandelt den Fall, in dem die 'std :: -Funktion 'fortbesteht. Wenn wir * nicht * speichern, nur aus einem Lambda erzeugen, haben 'function const 'und' function' den gleichen Overhead. – Yakk

+0

Ah, ich sehe! Dies hängt natürlich davon ab, was außerhalb von 'run_in_ui_thread()' passiert. Gibt es nur eine Unterschrift, die sagt "Pass by reference, aber ich werde die Adresse nicht speichern"? – ceztko

Verwandte Themen