Angenommen, ich habe eine Funktion, die Folgendes übernimmt std::function
:
void callFunction(std::function<void()> x)
{
x();
}
Soll ich x
stattdessen durch const-reference gehen?:
void callFunction(const std::function<void()>& x)
{
x();
}
Ändert sich die Antwort auf diese Frage abhängig davon, was die Funktion damit macht? Zum Beispiel, wenn es sich um eine Klassenelementfunktion oder einen Konstruktor handelt, der die std::function
in einer Elementvariablen speichert oder initialisiert .
sizeof(std::function)
, dass es nicht mehr als ist2 * sizeof(size_t)
, was die kleinste Größe ist, die Sie jemals für eine konstante Referenz in Betracht ziehen würden.std::function
Wrappers so wichtig ist wie die Komplexität des Kopierens. Wenn es sich um tiefe Kopien handelt, kann dies weitaus teurer sein als vorgeschlagensizeof
.move
die Funktion in?operator()()
istconst
so eine const Referenz sollte funktionieren. Aber ich habe std :: function noch nie benutzt.Antworten:
Wenn Sie Leistung wünschen, übergeben Sie den Wert, wenn Sie ihn speichern.
Angenommen, Sie haben eine Funktion namens "Führen Sie dies im UI-Thread aus".
Das führt einen Code im "UI" -Thread aus und signalisiert dann,
future
wann fertig. (Nützlich in UI-Frameworks, in denen sich der UI-Thread dort befindet, wo Sie mit UI-Elementen herumspielen sollen.)Wir haben zwei Unterschriften, die wir in Betracht ziehen:
Nun werden wir diese wahrscheinlich wie folgt verwenden:
Dadurch wird ein anonymer Abschluss (ein Lambda) erstellt, ein
std::function
Out daraus erstellt, an dierun_in_ui_thread
Funktion übergeben und gewartet, bis die Ausführung im Hauptthread abgeschlossen ist.In Fall (A) wird das
std::function
direkt aus unserem Lambda konstruiert, das dann innerhalb des verwendet wirdrun_in_ui_thread
. Das Lambda wirdmove
in denstd::function
Zustand gebracht, so dass jeder bewegliche Zustand effizient in ihn hineingetragen wird.Im zweiten Fall wird ein Temporär
std::function
erstellt, das Lambda wird darinmove
eingefügt, dann dieses Temporärstd::function
als Referenz innerhalb des verwendetrun_in_ui_thread
.So weit, so gut - die beiden arbeiten identisch. Außer das
run_in_ui_thread
wird eine Kopie seines Funktionsarguments erstellen, um es zur Ausführung an den UI-Thread zu senden! (Es wird zurückgegeben, bevor es damit fertig ist, daher kann es nicht einfach einen Verweis darauf verwenden.) Für Fall (A) legen wir einfachmove
dasstd::function
in seine Langzeitlagerung. In Fall (B) sind wir gezwungen, das zu kopierenstd::function
.Dieser Laden macht die Wertübergabe optimaler. Wenn die Möglichkeit besteht, dass Sie eine Kopie von speichern
std::function
, übergeben Sie den Wert. Ansonsten ist jeder Weg ungefähr gleichwertig: Der einzige Nachteil des Nebenwerts besteht darin, dass Sie denselben sperrigen nehmenstd::function
und eine Submethode nach der anderen verwenden. Ansonsten ist amove
so effizient wie aconst&
.Nun gibt es einige andere Unterschiede zwischen den beiden, die meistens auftreten, wenn wir einen anhaltenden Zustand innerhalb der haben
std::function
.Angenommen, das
std::function
Objekt wird mit einem gespeichertoperator() const
, es enthält jedoch auch einigemutable
Datenelemente, die geändert werden (wie unhöflich!).In diesem
std::function<> const&
Fall werden diemutable
geänderten Datenelemente aus dem Funktionsaufruf weitergegeben. In diesemstd::function<>
Fall werden sie nicht.Dies ist ein relativ seltsamer Eckfall.
Sie möchten
std::function
wie jeder andere möglicherweise schwere, billig bewegliche Typ behandeln. Umzug ist billig, Kopieren kann teuer sein.quelle
const&
sehe ich nur die Kosten für den Kopiervorgang.std::function
aus dem Lambda erstellt. In (A) wird das Temporäre in das Argument zu elidiertrun_in_ui_thread
. In (B) wird ein Verweis auf das Temporäre an weitergegebenrun_in_ui_thread
. Solange Ihrestd::function
s aus Lambdas als Provisorien erstellt werden, gilt diese Klausel. Der vorige Absatz befasst sich mit dem Fall, in dem dasstd::function
weiterhin besteht. Wenn wir nicht speichern, erstellen Sie einfach aus einem Lambdafunction const&
undfunction
haben genau den gleichen Overhead.run_in_ui_thread()
. Gibt es nur eine Unterschrift mit der Aufschrift "Referenzübergabe, aber ich werde die Adresse nicht speichern"?std::future<void> run_in_ui_thread( std::function<void()>&& )
Wenn Sie sich Sorgen um die Leistung machen und keine virtuelle Mitgliedsfunktion definieren, sollten Sie diese höchstwahrscheinlich überhaupt nicht verwenden
std::function
.Wenn Sie den Funktortyp zu einem Vorlagenparameter machen, können Sie ihn besser optimieren, als die
std::function
Funktorlogik einzuschließen. Der Effekt dieser Optimierungen wird wahrscheinlich die Bedenken hinsichtlich der Weitergabe von Kopien und Indirektionen bei der Übergabe erheblich überwiegenstd::function
.Schneller:
quelle
std::forward<Functor>(x)();
, die Wertekategorie des Funktors zu erhalten, da es sich um eine "universelle" Referenz handelt. In 99% der Fälle wird dies jedoch keinen Unterschied machen.callFunction(std::move(myFunctor));
std::move
wenn Sie ihn nicht mehr benötigen, oder direkt übergeben, wenn Sie das vorhandene Objekt nicht verlassen möchten. RegelncallFunction<T&>()
zum Reduzieren von Referenzen stellen sicher, dass ein Parameter vom TypT&
nicht vorhanden istT&&
.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.Durch Übergeben eines Werts können Sie das Argument in eine Variable verschieben (normalerweise eine Mitgliedsvariable einer Klasse):
Wenn Sie wissen, dass Ihre Funktion ihr Argument verschiebt, ist dies die beste Lösung. Auf diese Weise können Ihre Benutzer steuern, wie sie Ihre Funktion aufrufen:
Ich glaube, Sie kennen die Semantik von (Nicht-) Konstantenreferenzen bereits, daher werde ich den Punkt nicht näher erläutern. Wenn ich weitere Erklärungen dazu hinzufügen muss, fragen Sie einfach und ich werde aktualisieren.
quelle