Funkcja boost::bind
jest generalizacją funkcji std::bind1st
i std::bind2nd
dostępnych w bibliotece standardowej. Funkcja ta wspiera funktory, funkcje, wskaźniki do funkcji oraz wskaźniki do funkcji składowych. Jest w stanie związać dowolny argument ze specyficzną wartością albo przekierować argumenty wejściowe we wskazane pozycje. boost::bind
nie nakłada żadnych wymagań obiektom funkcyjnym, w ogólności nie wymaga standardowych definicji typów takich jak: result_type
, first_argument_type
, second_argument_type
.
Do korzystania z funcji boost::bind konieczne jest dołączenie nagłówka boost/bind.hpp
.
Zacznijmy od stworzenia dwóch prostych funkcji.
int g(int a, int b) { return a + b; } void f(int i1,int i2,int i3,int i4, int i5,int i6,int i7,int i8, int i9) { std::cout << i1 << i2 << i3 << i4 << i5 << i6 << i7 << i8 << i9 << '\n'; }
Najprostszym przypadkiem użycia boost::bind
jest stworzenie bezargumentowego funktora przez zastąpienie argumentów funkcji wartościami stałymi.
bind(g, 1, 2); //utworzony tutaj funktor ze stałymi arumentami odpowiada g(1, 2);
Jako pierwszy argument boost::bind
podajemy wskaźnik do funkcji, następne dwa przypisywane są jako pierwszy i drugi argument funkcji g
. Typ zwracany przez funktor jest identyczny z typem zwracanym przez funkcję podaną jako pierwszy argument.
Znacznie ciekawszym mechanizmem jest możliwość selektywnego zastępowania argumentów lub kolejności przekazywanych argumentów.
int main() { int i1=1,i2=2,i3=3,i4=4,i5=5,i6=6,i7=7,i8=8,i9=9; boost::bind(f,_9,_2,_1,_6,_3,_8,_4,_5,_7) //utworzenie tymczasowego funktora i wywołanie (i1,i2,i3,i4,i5,i6,i7,i8,i9); //odpowiadające wywołaniu f(i9, i2, i1, i6, i3, i8, i4, i5, i7) //w wyniku otrzymamy ciąg: 921638457 return 0; }
Realizujemy to za pomocą symbli zastępczych oznaczanych jako _1
, _2
, …, _9
. Dostępnych mamy łacznie 9 takich symboli. Ogranicza to możliwośc tworzenia funktorów do funktorów 9 elementowych (w przypadku funkcji składowych jest to 8 argumentów; musimy pamietć o przekazaniu obiektu na rzecz, którego będzie wywoływany funktor). Symbol _1
oznacza „zastąp przez pierwszy argument wejściowy”, analogicznie pozostałe osiem.
Argumenty można dowolnie zamieniać ze stałymi jak i zamieniać ich kolejność.
boost::bind
nie jest ograniczony tylko do wskaźników do funkcji, akceptuje min. również funktory. W przypadku użycia funktora zazwyczaj należy jawnie podać typ zwracany przez operator()
funktora (ewentualnie zdefinjować typ result_type
w funktorze).
struct Foo { int operator()(int a, int b) { return a - b; } bool operator()(long a, long b) { return a == b; } }; Foo f; int x = 1024; bind<int>(f, _1, _1)(x); //wywołanie f(x, x), wynik 0
Niektóre kompilatory mają problem ze składnią w postaci bind<R>(…)
. Wyrażenie to można zastąpić postacią alternatwną.
boost::bind(boost::type<int>(), f, _1, _1)(x);
Wskaźniki do metod i danych sładowych nie są obiektami funkcyjnymi, ponieważ nie wspierają operatora operator()
. Dla wygody boost::bind
akceptuje te wskażniki jako pierwszy argument. boost::bind
używa funkcji boost::mem_fn
do konwersji wskźników do składowych na funktory. Innymi słowami, wyrażenie:
bind(&Foo::f, args)
odpowiada
bind<R>(mem_fn(&Foo::f), args)
gdzie R
jest typem zwracanym przez Foo::f
w przypadku metod lub typem danych w przypadku danych składowych.
Przykład:
struct Foo { bool f(int a); }; Foo x; shared_ptr<Foo> p(new Foo); int i = 5; bind(&Foo::f, boost::ref(x), _1)(i); // x.f(i) bind(&Foo::f, &x, _1)(i); //(&x)->f(i) bind(&Foo::f, x, _1)(i); //(wewnętrzna kopja x).f(i) bind(&Foo::f, p, _1)(i); //(wewnętrzna kopja p)->f(i)
Jako drugi argument boost::bind
przekazujemy obiekt klasy na rzecz, której ma być wywoływana funkcja (jest to oczywiste, jeśli zauważymy, że pierwszym niejawnym argumentem metod jest zawsze wskaźnik this - adres obiektu). Najciekawsze są dwie ostatnie linijki przykładu. bind(&Foo::f, x, _1)
przechowuje wewnętrzną kopję obieku x
(warto zwrócić na to uwagę, szczególnie gdy kopjowanie obiektu jest kosztowne), natomiast bind(&Foo::f, p, _1)
kopję wskażnika p
(p
jest sprytnym wskaźnikiem shared_ptr<>
), tworzony obiekt funkcyjny przechowuje swoją referencję do instancji klasy Foo i będzie ona poprawna nawet kiedy p wyjdzie poza zasięg lub wykonamy na p reset()
.
Funcję boost::bind
można też bezprolemowo użyć z funkcjami wirtualnymi.
class base { public: virtual void print() const { std::cout << "base!\n"; } virtual ~base() {} }; class derived : public base { public: void print() const { std::cout << "derived!\n"; } }; int main() { derived d; base b; base* bp = &d; boost::bind(&base::print, _1)(b); boost::bind(&base::print, _1)(bp); boost::bind(&base::print, _1)(d); return 0; }
Wynikiem będzie oczekiwany ciąg:
base! derived! derived!
Część argumentów przekazywanych bind może być zagnieżdżonymi wyrażeniami boost::bind
.
bind(f, bind(g, _1))(x); //f(g(x))
Wewnętrzne wyrażenia są ewaluowane w bliżej nie okreśłonej kolejności, ale przed wywołaniem funktora zewnętzrnego boost::bind
. Odnosząc się do przykładu, kiedy utworzony funktor jest wywoływany z argumentem x
, w pierwszej kolejności ewalowany jest bind(g, _1)(x)
, dając g(x)
, następnie bind(f, g(x))(x)
, dając finalny wynik w postaci wywołania f(g(x))
.
Ta cecha boost::bind
może być bardzo dobrze zastosowana do kompozycji funkcji.
Trzeba zaznaczyć, że pierwszy argument boost::bind
(opakowywany obiekt funkcyjny) nie jest ewalowany, nawet wtedy, kiedy jest to obiekt funkcyjny produkowany przez zagnieżdżone wywołanie bind
lub argument w postaci symbola zastępczego. Poniższy przykład nie będzie działał tak, jakbyśmy tego oczekiwali.
typedef void (*fp)(int); std::vector<fp> v; std::for_each(v.begin(), v.end(), bind(_1, 5));
Oczekiwany efekt uzyskamy korzystając z pomocniczego obiektu funkcyjnego apply
(implementacja apply
w boost/bind/apply.hpp
), który podajemy jako pierwszy argument boost::bind
. Poprzedni przykład powinien wyglądać następująco.
typedef void (*fp)(int); std::vector<fp> v; std::for_each(v.begin(), v.end(), bind(apply<void>(), _1, 5));
Używając bind
można komponować funkcje korzystając naraz z boost::bind
i funkcji (algorytmów) z biblioteki standardowej.
std::vector<int> ints; ints.push_back(7); ints.push_back(4); ints.push_back(12); ints.push_back(10); int count=std::count_if( ints.begin(), ints.end(), boost::bind( std::logical_and<bool>(), boost::bind(std::greater<int>(),_1,5), boost::bind(std::less_equal<int>(),_1,10))); std::cout << count << '\n'; std::vector<int>::iterator int_it = std::find_if( ints.begin(), ints.end(), boost::bind(std::logical_and<bool>(), boost::bind(std::greater<int>(),_1,5), boost::bind(std::less_equal<int>(),_1,10))); if (int_it != ints.end()) { std::cout << *int_it << '\n'; }
Jak widać na przykładzie łatwo można tworzyć złożone predykaty. Często trzeba się jednak często zastanowić, czy stosować bardzo złożone wyrażenia. Utrudniają one późniejszą analizę kodu, często wręcz uniemożliwiają. Warto rozważyć w takich przypadkach zdefinjowanie funktora lub operatora operator()
dla klasy.
Funktor zwracany przez boost::bind
można przypisać do obiektu funkcyjnego boos::function
. W ten sposób można między innymi przechowywć wcześniej utworzone fukntory jako zmienne i pola klasy albo przekazywać je jako argument do konstruktorów lub funkcji. Przypisania możemy dokonać wykłym operatorem =
.
double Foo(double x, double y) { return x + y; } boost::function<double(double)> f = boost::bind(Foo, _1, y); //tworzymy funktor przyjmujący jeden argument //i przypisujemy go do obiektu funkcyjnego
Warunkiem poprawnej kompilacji jest dokładne określenie typu szablonu obiektu function (zasada działania boost::function
wykracza poza ten artykuł).
Przeciązone operatory dla boost::bind
pojawiły się w Boost 1.33
Obiekty funkcyjne produkowane przez boost::bind
przeciążają logiczny operator przecenia !
oraz operatory relacji ==
, !=
, <
, ⇐
, >
, >=
.
!bind(f, ...)
Jest ekwiwalentem
bind(logical_not(), bind(f, ...))
gdzie logical_not
jest funktorem przyjmującym jeden argument x
i zwracający !x
.
bind(f, ...) op x
op
jest operatorem relacji, wyrażniu temu odpowiada
bind(relation(), bind(f, ...), x)
gdzie relation
jest funktorem przyjmującym dwa argumenty a
i b
oraz zwracającym a op b
.
Przeciążenie tych operatorów umożliwia na konwencjonalne negowanie wyniku boost::bind
:
std::remove_if(first, last, !bind(&X::foo, _1)); //usun obiekt nie spełniający jakiegoś warunku
oraz na porównywanie wyników boost::bind
z wartościami:
std::find_if(first, last, bind(&X::name, _1) == "xyz");
ze znakiem zastępczym:
bind(&X::name, _1) == _2
albo z innym wyrażeniem boost::bind
:
std::sort(first, last, bind(&X::name, _1) < bind(&X::name, _2));
boost::bind
umożliwia w przeciwnieństwie do funkcji z biblioteki standardowej (służących do tworzenia adpterów funkcji) bardzo elastyczną pracę z kodem.
#include <iostream> #include <string> #include <boost/bind.hpp> class status { std::string name_; bool ok_; public: status(const std::string& name):name_(name),ok_(true) {} void break_it() { ok_=false; } bool is_broken() const { return ok_; } void report() const { std::cout << name_ << " is " << (ok_ ? "working nominally":"terribly broken") << '\n'; } }; int main() { std::vector<status*> statuses; statuses.push_back(new status("status 1")); statuses.push_back(new status("status 2")); statuses.push_back(new status("status 3")); statuses.push_back(new status("status 4")); statuses[1]->break_it(); statuses[2]->break_it(); std::for_each( statuses.begin(), statuses.end(), boost::bind(&status::report, _1)); return 0; }
Jeśli w pewnym momencie pisania projektu postanowimy używać wektora wskaźników do obiektów zamiast wektora obiektów lub sprytnych wskaźników w przypadku bind musimy zmienić tylko moment tworzenia obiektów, na przykład:
std::vector<boost::shared_ptr<status> > statuses; statuses.push_back( boost::shared_ptr<status>(new status("status 1"))); statuses.push_back( boost::shared_ptr<status>(new status("status 2"))); statuses.push_back( boost::shared_ptr<status>(new status("status 3"))); statuses.push_back( boost::shared_ptr<status>(new status("status 4")));
Użycie funkcji standardowych (std::mem_fun_ref
i std::mem_fun
) zmusza do zmian także pętli for_each
po każdej modyfikacji typu przechowywanego przez wektor, a nawet uniemożliwia dalszą pracę (brak obsługi w przypadku użycia sprytnych wskaźników).
— Maciej Płachta H1ISI