std::function



  • Hallo ihr Lieben,

    auch auf die Gefahr hin, dass ich mich wieder blamiere, aber: Was ist so toll an std::function (zusammen mit std::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 mit function zu tun. Die von bind erzeugten Objekte erfüllen nur zufällig die von einer passenden function geforderte Schnittstelle.

    Statt bind sollte man in C++14 lieber Lambdas verwenden. Das spart beispielsweise in function 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 benutzt function 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 mit std::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 in function 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: 16

    Lambda ohne bind ist am kleinsten und hat keinen Speicher-Overhead. Das kann in einer function 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 in function 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 in function 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 eine function steckt. Dann finden sich die unnötigen Kopien nämlich auf dem Heap wieder. function wird typischerweise so implementiert, dass kleine Funktoren im function -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 die bind -Ä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.


  • Mod

    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 ist bind() 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.


Anmelden zum Antworten