Nehmen wir an, ich habe eine Funktion, die einen std::function
Annimmt:
void callFunction(std::function<void()> x)
{
x();
}
Soll ich stattdessen x
als const-Referenz übergeben ?:
void callFunction(const std::function<void()>& x)
{
x();
}
Ändert sich die Antwort auf diese Frage je nachdem, was die Funktion damit macht? Zum Beispiel, wenn es sich um eine Klassenmitgliedsfunktion oder einen Konstruktor handelt, die bzw. der das std::function
In einer Mitgliedsvariablen speichert oder initialisiert.
Wenn Sie Leistung wünschen, übergeben Sie den Wert, wenn Sie ihn speichern.
Angenommen, Sie haben eine Funktion namens "dies im UI-Thread ausführen".
std::future<void> run_in_ui_thread( std::function<void()> )
das führt einen Code im "ui" -Thread aus und signalisiert dann das future
, wenn es fertig ist. (Nützlich in UI-Frameworks, in denen sich der UI-Thread befindet, in dem Sie mit UI-Elementen herumspielen sollen.)
Wir haben zwei Unterschriften, über die wir nachdenken:
std::future<void> run_in_ui_thread( std::function<void()> ) // (A)
std::future<void> run_in_ui_thread( std::function<void()> const& ) // (B)
Nun werden wir diese wahrscheinlich wie folgt verwenden:
run_in_ui_thread( [=]{
// code goes here
} ).wait();
dadurch wird ein anonymer Abschluss (ein Lambda) erstellt, ein std::function
daraus erstellt, an die run_in_ui_thread
- Funktion übergeben und darauf gewartet, dass der Hauptthread vollständig ausgeführt wird.
In Fall (A) wird der std::function
Direkt aus unserem Lambda konstruiert, das dann innerhalb des run_in_ui_thread
Verwendet wird. Das Lambda ist move
d in std::function
, So dass jeder bewegliche Zustand effizient hineingetragen wird.
Im zweiten Fall wird ein temporäres std::function
Erstellt, in das das Lambda move
d eingefügt wird, und dieses temporäre std::function
Wird als Referenz innerhalb des run_in_ui_thread
Verwendet.
So weit, so gut - die beiden sind identisch. Mit der Ausnahme, dass run_in_ui_thread
Eine Kopie seines Funktionsarguments erstellt, um es zur Ausführung an den UI-Thread zu senden. (Es wird zurückgegeben, bevor es fertig ist, daher kann es nicht einfach einen Verweis darauf verwenden.) Für den Fall (A) move
geben wir einfach den std::function
In seinen Langzeitspeicher ein. In Fall (B) müssen wir den std::function
Kopieren.
Dieser Laden optimiert das Vorbeigehen an Werten. Wenn die Möglichkeit besteht, dass Sie eine Kopie des std::function
Speichern, übergeben Sie den Wert. Andernfalls ist jeder Weg ungefähr gleichwertig: Der einzige Nachteil von by-value besteht darin, dass Sie dieselbe sperrige std::function
- Methode verwenden und eine Submethode nach der anderen verwenden. Ansonsten ist ein move
genauso effizient wie ein const&
.
Nun, es gibt einige andere Unterschiede zwischen den beiden, die meistens eintreten, wenn wir einen beständigen Zustand innerhalb von std::function
Haben.
Angenommen, das std::function
Speichert ein Objekt mit einer operator() const
, aber es enthält auch einige mutable
Datenelemente, die es modifiziert (wie unhöflich!).
Im Fall std::function<> const&
Werden die geänderten mutable
-Datenmember aus dem Funktionsaufruf weitergegeben. Im Fall std::function<>
Werden sie nicht.
Dies ist ein relativ seltsamer Eckfall.
Sie möchten std::function
Wie jeden anderen möglicherweise schwergewichtigen, billig beweglichen Typ behandeln. Umzug ist billig, Kopieren kann teuer sein.
Wenn Sie sich Sorgen um die Leistung machen und keine Funktion für virtuelle Mitglieder definieren, sollten Sie höchstwahrscheinlich überhaupt nicht std::function
Verwenden.
Indem Sie den Funktortyp zu einem Vorlagenparameter machen, können Sie eine größere Optimierung als std::function
Durchführen, einschließlich der Inlinierung der Funktorlogik. Die Auswirkungen dieser Optimierungen überwiegen wahrscheinlich bei weitem die Bedenken hinsichtlich der Übergabe von std::function
.
Schneller:
template<typename Functor>
void callFunction(Functor&& x)
{
x();
}
Wie in C++ 11 üblich, hängt die Übergabe von value/reference/const-reference davon ab, was Sie mit Ihrem Argument tun. std::function
ist nicht anders.
Übergabe nach Wert ermöglicht Ihnen das Verschieben des Arguments in eine Variable (normalerweise eine Mitgliedsvariable einer Klasse):
struct Foo {
Foo(Object o) : m_o(std::move(o)) {}
Object m_o;
};
Wenn Sie wissen, dass Ihre Funktion ihr Argument verschieben wird, ist dies die beste Lösung. Auf diese Weise können Ihre Benutzer steuern, 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 glaube, Sie kennen die Semantik von (Nicht-) Konstantenreferenzen bereits, daher werde ich den Punkt nicht näher erläutern. Wenn Sie weitere Erläuterungen dazu benötigen, fragen Sie einfach und ich werde aktualisieren.