Narzędzia użytkownika

Narzędzia witryny


bind

To jest stara wersja strony!


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. 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 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 bind podajemy wskaźnik do funkcji, następne argumenty 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(&nine_arguments,_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
}
bind(f, _2, _3, _1, _1, 0, 0, _3, 0, _2)(x, y, z);  //utworzenie tymczasowego funktora i wywołanie
 

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. Jak wynika z przykładu, można dowolnie „mieszać” argumenty ze stałymi jak i ich kolejność.

Użycie z funktorami

bind nie jest ograniczony tylko do funkcji, akceptuje min. również funktory. W przypadku użycia funktora zazwyczaj trzeba 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 bind akceptuje te wskażniki jako pierwszy argument. Bind używa funkcji boost::mem_fn do przekonwertowania wskźników do składowych na funktory. Innymi słowy, 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 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ę 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);

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 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 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 bind może być bardzo dobrze zastosowana do kompozycji funkcji.

Trzeba zaznaczyć, że pierwszy argument 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 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 bind i funkcji 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ą z biblioteką Boost.Function

Funktor zwracany przez bind można przypisać do obiektu funkcyjnego boos::function. W ten sposób można mięzy innymi przechowywć wcześniej utworzone fukntory lub przekazywać je jako argument do konstruktorów, 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 bind pojawiły się w Boost 1.33

Obiekty funkcyjne produkowane przez 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
<code>
op jest operatorem relacji, wyrażniu temu odpowiada
 
<code cpp>
bind(relation(), bind(f, ...), x)

gdzie relation jest funktorem przyjmującym dwa argumenty a i b i zwracającym a op b.

What this means in practice is that you can conveniently negate the result of bind:

Przeciążenie tych operatorów umożliwia na konwencjonalne negowanie wyniku bind:

std::remove_if(first, last, !bind(&X::foo, _1));  //usun obiekt nie spełniający jakiegoś warunku

and compare the result of bind against a value: oraz na porównywanie wyników 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 bind:

std::sort(first, last, bind(&X::name, _1) < bind(&X::name, _2));

Inne przykłady użycia

Bind umożliwia w przeciwnieństwie do funkcji z biblioteki standardowej

bind.1208019403.txt.gz · ostatnio zmienione: 2008/04/12 18:56 przez maciejp