2016-04-14 3 views
3

This answerstd::bind gibt ein Objekt nach Wert zurück und this comment bedeutet, dass die Zuweisung an std::function eine Heap-Zuweisung zum Speichern des von std::bind zurückgegebenen Werts verursacht.Ist es möglich, die Ausgabe von `std :: bind` direkt als Wert zu akzeptieren, ohne in std :: function umzuwandeln?

Gibt es eine Möglichkeit, diese Heap-Zuweisung zu vermeiden und den Rückgabewert std::bind direkt an einen anderen Wert zu übergeben?

Wenn ja, was würde die Methodensignatur ersetzen std::function mit?


Um expliziter zu sein, habe ich eine Funktion wie folgt.

void runThisFunction(std::function<void()> func); 

Angenommen, es ist eine Funktion, mit der folgenden foo Signatur.

void foo(int a, int b); 

Jetzt würde ich runThisFunction wie folgt aufrufen.

runThisFunction(std::bind(foo, 1, 2)); 

In diesem Aufruf wird der Ausgang von std::bind in eine std::function umgewandelt, und die dynamische Speicherzuordnung auftritt als Teil dieses Prozesses.

Ist es möglich, std::function durch eine andere Deklaration zu ersetzen, die die Ausgabe von std::bind direkt per Wert erhalten würde, wodurch die dynamische Speicherzuweisung vermieden wird?

+2

hat 'Vorlage Leere runThisFunction (T func)' zählen? –

+0

@PiotrSkotnicki, Das fühlt sich an wie es funktionieren könnte. Es leidet unter dem üblichen Problem mit Templates (Erzeugen einer Kopie des Codes für jeden Typ), aber ich nehme an, es kann nicht geholfen werden. – merlin2011

+2

Nicht sicher, warum diese Frage abgelehnt wurde. Es kam mir einfach nicht in den Sinn, eine Vorlage zu verwenden, um dieses Problem zu lösen, obwohl es im Nachhinein offensichtlich erscheint. – merlin2011

Antwort

4

Ist es möglich, std::function mit einer anderen Erklärung zu ersetzen, die Ausgabe von std::bind direkt dabei von Wert erhalten würde, würde die dynamische Speicherzuweisung vermieden werden?

Ja ist es; aber der Typ, der von std::bind zurückgegeben wird, ist nicht angegeben. Daher müssen Sie eine Vorlage verwenden, um den Typ zu erfassen.

template <typename F> 
void runThisFunction(F func); 

Auf der Speicherzuweisung ...

In diesem Aufruf wird der Ausgang von std::bind in eine std::function umgewandelt, und die dynamische Speicherzuordnung auftritt als Teil dieses Prozesses.

dynamische Speicher verwendet werden (aber nicht always), hängt es von der Größe des Funktor in die std::function und der Qualität der Umsetzung gebunden zu sein.

Ferner hat die C++ - Spezifikation diese §20.12.12.2.1/11;

[Anmerkung: Implementationen sind aufgefordert, die Verwendung von dynamisch zugewiesenen Speicher für kleine aufrufbare Objekte, beispielsweise zu vermeiden, wo f ein Objekt nur einen Zeiger oder eine Referenz auf ein Objekt hält und einen Mitgliedsfunktionszeiger. - Endnote]

Ich wäre nicht zu besorgt über die Speicherzuweisung, selbst wenn es eine gibt. Wenn der Code nicht leistungskritisch ist und Sie ihn als solchen gemessen haben, sollte die erforderliche Indirektion kein Problem darstellen.

Denken Sie daran, dass in Ihrem Fall die foo gebunden in der bind ein Zeiger ist und es wahrscheinlich ist, dass es keine dynamische Speicherzuweisung sowieso geben würde.


Ich fing an, diese suchen, weil ich Cache-Misses auf die Umstellung auf

durch Instrumentierung erfasst unerwartete Langsamkeit aufgrund gemessen

So haben Sie einige gemessen Sorgen über die Leistung ... es gibt Alternativen zu verwenden std::bind gepaart mit std::function. std::bind ist ein nützliches Allzweck-Bindemittel, aber das bedeutet nicht, dass es auch leistungsfähig genug ist - machen Sie es selbst. Ein kundenspezifischer Funktor könnte leistungsfähiger sein. Eine lambda-basierte Implementierung wäre auch gut anzusehen. Vergessen Sie auch nicht, dass die Funktion foo auch mit std::function verwendet werden kann und Sie dann komplett auf den Funktor/Binder verzichten (Achtung, die Signaturen müssen übereinstimmen).


Eine Randnotiz auf, wie „klein“ das Objekt muss vor den „kleines Objekt“ Optimierungen im Zitat oben Kick erwähnt wird zwischen den Bibliothek-Implementierungen ruhig ein bisschen zu variieren scheint.

hier auf coliru (libstdC++), die Größe des Arguments für std::function 16 Byte sein muss oder weniger, auf MSVC beträgt die Grenze 32 Byte (beide diese aussehen 32-Bit-Plattformen zu sein). Mit einer clang ++ (libC++) 64-Bit-Kompilierung beträgt dieses Limit 24 Byte ... Es hängt wirklich von der Implementierung ab, wie viel Speicherplatz dafür zur Verfügung steht, bevor new Allokationen vorgenommen werden müssen.

Ich bin nicht sicher, wie kritisch die Leistung ist, aber die Berechnung dieser Grenze für Ihre Ziele könnte auch durchgeführt werden und dann Optimierungen angewendet werden, so dass die Argumente für die std::function unter diesen Grenzen gehalten werden; z.B. Verwenden eines Zeigers oder einer Referenz (auch std::ref) zu einem struct für Argumente (aber es muss darauf geachtet werden, dass diese nicht hängen gelassen werden).

+0

Ich fing an, das zu betrachten, weil ich Cache-Misses auf der Umwandlung wegen der unerwarteten Langsamkeit gemessen durch Instrumentation gemessen habe. Ich denke, dass diese Antwort funktionieren wird, aber ich werde akzeptieren, nachdem ich es morgen getestet habe. Vielen Dank! – merlin2011

+0

Diese Antwort funktioniert, aber das Erstellen der Empfangsfunktion templated erweist sich als schmerzhafter als erwartet, weil es bedeutet, eine Menge von Zustand in eine Header-Datei ziehen, die ich ursprünglich in der cc-Datei versteckt hatte. – merlin2011

+0

Es ist einer der Nachteile, nicht spezifizierte Typen tendieren dazu. Ich würde sagen, Sie könnten ein Redesign untersuchen, aber das ist ein ganz anderes Problem und könnte auch übertrieben sein. – Niall

1

Wenn die Leistung wichtig ist, aber Sie wollen immer noch eine konkrete Schnittstelle, sollte die Bindung an einen Lambda eher als ein Bindemittel Objekt (über std :: bind).

lief ich diesen Test auf gcc 5.3 (libstdC++, -O2)

#include <functional> 

void foo(int, int); 

void runThisFunction(std::function<void()> func); 

void test() 
{ 
    runThisFunction(std::bind(&foo, 1, 2)); 
} 

void test1() 
{ 
    runThisFunction([]{ foo(1, 2); }); 
} 

test() Ergebnisse in einem Aufruf von new.Jedoch ist die kleine Funktionsoptimierung in std::function der Lage zu erkennen, dass das Lambda in test1() klein genug ist, und ein Aufruf an new nicht in den Code emittiert:

(Anmerkung: Ausnahmebehandlungscode zur Klarheit entfernt):

std::_Function_base::_Base_manager<test1()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<test1()::{lambda()#1}> const&, std::_Manager_operation): 
     testl %edx, %edx 
     je  .L3 
     cmpl $1, %edx 
     jne  .L2 
     movq %rsi, (%rdi) 
.L2: 
     xorl %eax, %eax 
     ret 
.L3: 
     movq typeinfo for test1()::{lambda()#1}, (%rdi) 
     xorl %eax, %eax 
     ret 
std::_Function_handler<void(), test1()::{lambda()#1}>::_M_invoke(std::_Any_data const&): 
     movl $2, %esi 
     movl $1, %edi 
     jmp  foo(int, int) 
test1(): 
     subq $40, %rsp 
     movq %rsp, %rdi 
     movq std::_Function_handler<void(), test1()::{lambda()#1}>::_M_invoke(std::_Any_data const&), 24(%rsp) 
     movq std::_Function_base::_Base_manager<test1()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<test1()::{lambda()#1}> const&, std::_Manager_operation), 16(%rsp) 
     call runThisFunction(std::function<void()>) 
     movq 16(%rsp), %rax 
     testq %rax, %rax 
     je  .L7 
     movl $3, %edx 
     movq %rsp, %rsi 
     movq %rsp, %rdi 
     call *%rax 
.L7: 
     addq $40, %rsp 
     ret 
typeinfo for test1()::{lambda()#1}: 
typeinfo name for test1()::{lambda()#1}: 
+0

Die Garantie für keine Zuweisung in 'std :: function' ist für Zeiger und' reference_wrapper' (und wird für kleine Objekte empfohlen). Ihr Lambda erfasst hier nichts (kann also im Funktionszeiger umgewandelt werden). Mit der Erfassung können sich die Dinge unterscheiden. – Jarod42

+0

@ Jarod42 ja, die kleine Funktion Optimierung wird eine Grenze für seine Aggressivität abhängig von der Standard-Bibliothek Implementierung haben. Ich kann jedoch nicht über Zukunftsszenarien spekulieren, die der OP haben könnte - das ist der, den er präsentierte. –

+0

Es ist interessant zu wissen, dass Lambda unter bestimmten Umständen besser funktioniert, wenn man bedenkt, dass ich zu 'std :: bind' gewechselt habe, weil [lambda nicht unter gcc funktionierte] (http://stackoverflow.com/questions/35474417/ ist-es-möglich-eine Variable-Anzahl-von-Parametern-in-Lambda-zu erfassen. – merlin2011

1

von Referenz Wrapper verwenden, sollten Sie die Heapzuordnung vermeiden:

auto f = std::bind(foo, 1, 2); 
runThisFunction(std::ref(f)); 

Dies, weil reference_wrapper ein kleines Objekt ist und std::function wird gefördert zu vermeiden Zuteilung für kleine (siehe [func.wrap.func.con]):

Hinweis: Implementationen werden ermutigt, die Verwendung von dynamisch zugewiesenen Speicher für kleine aufrufbare Objekte, zum Beispiel zu vermeiden, wo die f Ziel ist ein Objekt, das nur einen Zeiger oder Verweis auf ein Objekt und einen Mitgliedsfunktionszeiger enthält.

Es funktioniert nur, wenn runThisFunction() nicht den Wert der std::function für den Aufruf nach der Lebensdauer von f speichern.

+1

Gepflegte Alternative mit dem 'std :: ref'. Ich denke nicht, dass ein einfacher Aufruf der Funktion alles ist, was die 'runThisFunction' tut, daher muss dies mit der Einschränkung einhergehen, dass die Bindung' f' mindestens so lange am Leben bleiben muss, wie '' runThisFunction'' erfordert std :: function' (und was auch immer 'runThisFunction' der Funktion zuführt, zB eine Liste irgendeiner Art). – Niall

-1

sehr einfach (zumindest für gcc):

int someFunc(int a, int b) { return a+b; } 
//.... 
int b = (std::bind(someFunc,3,4))(); // returns 7 
Verwandte Themen