Narzędzia użytkownika

Narzędzia witryny


bind

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<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);

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<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!

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<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.

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<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ąż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 <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

bind.txt · ostatnio zmienione: 2008/04/14 09:00 przez maciejp