wake-up-neo.net

Soll ich eine std :: -Funktion mit const-reference übergeben?

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.

120
Sven Adbring

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 moved 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 moved 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();
}
31
Ben Voigt

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.

23
syam