std::function
-
Hallo ihr Lieben,
auch auf die Gefahr hin, dass ich mich wieder blamiere, aber: Was ist so toll an
std::function
(zusammen mitstd::bind
?)Ich kenne Funktionen und Funktoren und weiß, dass der Vorteil eines Funktors z.B. der Zustand ist, den ein Funktor einnehmen kann. Das war schon öfter nützlich.
Aber wie passt da
std::function
rein?Gruß
-- Klaus.
-
Der "normale" Weg Functoren zu benutzen ist ungefähr so:
template<class Iter, class Func/* <-- */> void for_each(Iter begin, Iter end, Func fn) { for( ; begin != end; ++begin) fn(*begin); }
So ist auch die STL aufgebaut. Das schöne ist, du (bzw. der der das hier geschrieben hat) braucht sich keine Sorgen um irgendwelche Typen zu machen. Durch die Templates ist das alles abgedeckt:
void fn_pointer(int i) {} struct Functor { void operator()(int i) {} }; auto lmbda = [](int i){}; int main() { std::vector<int> myVec; for_each(begin(myVec), end(myVec), fn_pointer); for_each(begin(myVec), end(myVec), Functor()); for_each(begin(myVec), end(myVec), lmbda); return 0x0; }
Aber versuch das ganze mal Laufzeitdynamisch zu machen. Den Typ von nem Lambdaausdruck zu holen (ohne decltype oder auto) ist quasi nicht möglich und auch unsinnig.
std::function kapselt solche Konstrukte ein. Du kannst einen Lambda-Ausdruck, einen Funktor oder einen Funktionszeiger reinstecken und hast dieselbe Funktionalität, Semantik und Syntax.
std::bind geht dann noch weiter und ordnet Parameter fest zu. Auf das Beispiel oben bezogen, stell dir folgendes vor:
void foo_ptr(int i /* soweit so gut */, double d, char c /* nicht mehr so gut */) { // ... } int main() { std::vector<int> myVec; for_each(begin(myVec), end(myVec), foo_ptr); // <--- geht nicht, weil die Parameter nicht stimmen! // Lösung: for_each(begin(myVec), end(myVec), std::bind(foo_ptr, 3.1415927, _2, 'x', _3)); // Syntax bin ich mir grad nicht sicher }
Aber nach dem bind ist das Funktionsobjekt (jetzt in std::function eingekapselt) mit einem Parameter aufrufbar.
-
Skym0sh0 schrieb:
Aber nach dem bind ist das Funktionsobjekt (jetzt in std::function eingekapselt) mit einem Parameter aufrufbar.
bind
hat gar nichts mitfunction
zu tun. Die vonbind
erzeugten Objekte erfüllen nur zufällig die von einer passendenfunction
geforderte Schnittstelle.Statt
bind
sollte man in C++14 lieber Lambdas verwenden. Das spart beispielsweise infunction
einen Funktionszeiger an Speicher.Das Wichtige an
function
ist die Entkopplung von Konstruktion und Verwendung. Der Aufrufer muss und kann nicht erfahren wie das Funktionsobjekt erzeugt worden ist. Man benutztfunction
typischerweise, wenn das Funktionsobjekt länger gespeichert werden soll, wofür der Typ nunmal auf irgendeiner Ebene fest sein muss. Temporäre Objekte reicht man besser mit Templates herunter.
-
Klaus82 schrieb:
Was ist so toll an
std::function
(zusammen mitstd::bind
?)Man kann damit funktionale Programmierung in C++ betreiben. Funktionen können partially applied (std::bind) und wie normale Werte herumgereicht (std::function) werden.
Mit mehr Aufwand geht sowas auch ohne diese features, aber es wird mit ihnen halt einfacher. Das Strategy-Pattern z.B. wird leichter lesbar wenn man nicht mehr wie blöde Rumableiten muss, sondern einfach ein passende neue Strategie-Funktion schreiben kann. Und nakte Funktionszeiger sind hässlich.
-
std::function ist ein nützliches Werkzeug, direkter Vererbung aus dem Weg zu gehen. Man erinnere sich: Vererbung ist die stärkste Form der Bindung, d.h. verhindert gute Kapselung.
Ausgangslage: Ich will eine Klasse schreiben, die zum ableiten vorgesehen ist (Java ist voll von solchen Klassen: Runnable, Callable, etc.)
1. -> Warum ist hier std::function nicht angemessen?
2. -> Anstatt std::function einen Template-Parameter nehmen.TyRoXx schrieb:
Statt
bind
sollte man in C++14 lieber Lambdas verwenden. Das spart beispielsweise infunction
einen Funktionszeiger an Speicher.Quelle? [Tipp: Es ist falsch]
-
#include <array> #include <iostream> #include <functional> int main() { typedef std::array<int, 1> bound_type; bound_type const bound{}; void (*c)(bound_type) = [](bound_type) {}; std::cerr << "function ptr: " << sizeof(c) << '\n'; auto a = [](bound_type) {}; std::cerr << "stateless lambda: " << sizeof(a) << '\n'; auto a_bound = std::bind(a, bound); std::cerr << "lambda bound with std::bind: " << sizeof(a_bound) << '\n'; auto b = [bound]() {}; std::cerr << "lambda-based closure: " << sizeof(b) << '\n'; auto c_bound = std::bind(c, bound); std::cerr << "function ptr bound with std::bind: " << sizeof(c_bound) << '\n'; }
GCC 4.8 schrieb:
function ptr: 8
stateless lambda: 1
lambda bound with std::bind: 8
lambda-based closure: 4
function ptr bound with std::bind: 16Lambda ohne
bind
ist am kleinsten und hat keinen Speicher-Overhead. Das kann in einerfunction
den Unterschied zwischen dynamischer Speicheranforderung und Small Functor Optimization ausmachen.std__function=virtual schrieb:
TyRoXx schrieb:
Statt
bind
sollte man in C++14 lieber Lambdas verwenden. Das spart beispielsweise infunction
einen Funktionszeiger an Speicher.Quelle? [Tipp: Es ist falsch]
Lambda spart sogar noch mehr als den Funktionszeiger, nämlich 4 Bytes Padding (warum auch immer GCC die einfügt).
-
TyRoXx schrieb:
std__function=virtual schrieb:
TyRoXx schrieb:
Statt
bind
sollte man in C++14 lieber Lambdas verwenden. Das spart beispielsweise infunction
einen Funktionszeiger an Speicher.Quelle? [Tipp: Es ist falsch]
Lambda spart sogar noch mehr als den Funktionszeiger, nämlich 4 Bytes Padding (warum auch immer GCC die einfügt).
Äh, ich hatte dich so verstanden, man sollte Lambdas verwenden um std::function zu erzeugen.
Wer die Wahl zwischen Lambdas und std::function hat, nimmt natürlich Lambdas. Allerdings nicht wegen dem Speicher-Overhead, der sowieso vernachlässigbar ist, sondern wegen der doppelten Indirektion.
-
std__function=virtual schrieb:
Äh, ich hatte dich so verstanden, man sollte Lambdas verwenden um std::function zu erzeugen.
Ja, das sollte man.
Man könnte sogar in diesem Fall Lambda benutzen, wenn man es
function
einfacher machen möchte://schlecht std::function<int ()>{std::rand}; //gut std::function<int ()>{[]{ return std::rand(); }};
Dass das Speicher spart, setzt leider noch Compiler-spezifische Erweiterungen voraus, die in der Standardbibliothek ausgenutzt werden. Keine Ahnung, ob das jemand so implementiert. Naja gut, mit Erweiterungen kann auch die erste Variante optimiert werden, also ein schlechtes Beispiel.
Mal sehen, ob man bei
bind
auch die Kopien der Konstanten wegbekommen kann:#include <iostream> #include <functional> #include <type_traits> #include <cstdio> template <class F, F f> struct constified { template <class ...Args> auto operator ()(Args &&...args) const { return f(std::forward<Args>(args)...); } }; int main() { auto put_a = std::bind(std::putc, 'a', stderr); std::cerr << "put_a: " << sizeof(put_a) << '\n'; //Der Funktionszeiger wird Teil des Funktortyps. //std::bind könnte dann Empty Base Optimization anwenden und man //hätte Speicher gespart. auto put_b = std::bind(constified<int (*)(int, FILE *), std::putc>(), 'b', stderr); std::cerr << "put_b: " << sizeof(put_b) << '\n'; auto put_c = [] { return std::putc('c', stderr); }; std::cerr << "put_c: " << sizeof(put_c) << '\n'; put_a(); put_b(); put_c(); std::cerr << '\n'; }
Mein GCC macht die Optimierung leider nicht. Entweder hat man die bisher vergessen oder der Standard verbietet sie aus irgendeinem Grund.
put_a: 24 put_b: 24 put_c: 1 abc
Lambdas machen das ohne jede Hilfe oder Optimierung der Standardbibliothek optimal.
-
Ich verstehe nicht, was du mit deinem zweiten Teil aussagen willst (es kommt nicht ein einziges mal der Typ std::function vor), aber std::bind so zu implementieren, dass es ein Lambda zurückgibt ist in C++14 trivial, deshalb sehe ich nicht ein, wie Lambdas da schneller sein können.
-
std__function=virtual schrieb:
Ich verstehe nicht, was du mit deinem zweiten Teil aussagen willst (es kommt nicht ein einziges mal der Typ std::function vor),
Ich will damit sagen, dass Konstanten von Lambdas automatisch richtig behandelt werden.
bind
kopiert Konstanten unnötigerweise und bläht den Funktor damit auf.
Das ist genau dann relevant, wenn man den Funktor in einefunction
steckt. Dann finden sich die unnötigen Kopien nämlich auf dem Heap wieder.function
wird typischerweise so implementiert, dass kleine Funktoren imfunction
-Objekt direkt gespeichert werden, also ohne dynamische Speicheranforderung und ohne Indirektion. Mit Lambdas ist die Wahrscheinlichkeit höher, dass man von dieser Optimierung profitiert, denn Lambda-Closures sind oft kleiner als diebind
-Äquivalente.std__function=virtual schrieb:
aber std::bind so zu implementieren, dass es ein Lambda zurückgibt ist in C++14 trivial, deshalb sehe ich nicht ein, wie Lambdas da schneller sein können.
Weil auch bei einem Lambda die Argumente gebunden werden müssen. Die gebundene Kopie von
f
wird Speicher verbrauchen:template <class F, class ...Args> auto bind(F f, Args ...args) { return [f, args...] { return f(args); }; }
Der Compiler kann so schlau sein das zu optimieren. Darauf würde ich mich aber nicht verlassen.
-
return [=] { return f(args...); };
Das haben wir schon einmal durchdiskutiert..
Ich will damit sagen, dass Konstanten von Lambdas automatisch richtig behandelt werden.
Lambdas verbrauchen Maschinencode. Der Closure-Typ ist eine Klasse mit überladenem operator(), welcher natürlich als gewöhnliche Funktion im Programm fungiert. Allerdings nur einmal, wer also den Funktor kopiert...
-
Ich sehe, das übersteigt mal wieder meine Fähigkeiten.
Ich benötige Funktionen vorwiegend für meine Numerik und muss sie in numerische Bibliothen stopfen, aus dem Grund bin ich immer bestrebt diese Schnittstelle möglichst einfach zu halten ... und zu verstehen.
Ich bin zuletzt auf
std::function
aufmerksamt geworden aus diesem Grund.Gruß,
-- Klaus.
-
Klaus82 schrieb:
Ich bin zuletzt auf std::function aufmerksamt geworden aus diesem Grund.
In dem Fall geht es darum, eine Klassenmethode mittels
std::function<>()
zu wrappen. Dort istbind()
noetig, um den this Zeiger mit der Methode zu binden, was, wie Tyroxx schon ganz am Anfang geschrieben hat, die Konstruktion (hier: methode einer Klasse) von deren Verwendung entkoppelt.