======Biblioteka Boost.Bind====== 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''. =====Użycie z funkcjami i wskaźnikami do funkcji===== 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ść. =====Użycie z funktorami===== ''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(f, _1, _1)(x); //wywołanie f(x, x), wynik 0 Niektóre kompilatory mają problem ze składnią w postaci ''bind(...)''. Wyrażenie to można zastąpić postacią alternatwną. boost::bind(boost::type(), f, _1, _1)(x); =====Użycie ze wskaźnikami do składowych===== 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(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 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! =====Użycie zagnieżdżeń do kompozycji fukcji===== 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 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 v; std::for_each(v.begin(), v.end(), bind(apply(), _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 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(), boost::bind(std::greater(),_1,5), boost::bind(std::less_equal(),_1,10))); std::cout << count << '\n'; std::vector::iterator int_it = std::find_if( ints.begin(), ints.end(), boost::bind(std::logical_and(), boost::bind(std::greater(),_1,5), boost::bind(std::less_equal(),_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. =====Użycie z biblioteką Boost.Function===== 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 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ążone operatory===== 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)); =====Inny przykład użycia===== ''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 #include #include 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 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 > statuses; statuses.push_back( boost::shared_ptr(new status("status 1"))); statuses.push_back( boost::shared_ptr(new status("status 2"))); statuses.push_back( boost::shared_ptr(new status("status 3"))); statuses.push_back( boost::shared_ptr(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).\\ \\ \\ --- //[[mplachta@stud.elka.pw.edu.pl|Maciej Płachta H1ISI]]//