======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]]//