Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Previous revision Next revision | Previous revision Next revision Both sides next revision | ||
bind [2008/04/12 18:30] maciejp |
bind [2008/04/14 08:53] maciejp |
||
---|---|---|---|
Linia 1: | Linia 1: | ||
======Biblioteka Boost.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. 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. | + | 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.hp”. | + | Do korzystania z funcji boost::bind konieczne jest dołączenie nagłówka ''boost/bind.hpp''. |
- | + | ||
Linia 24: | Linia 22: | ||
</code> | </code> | ||
- | Najprostszym przypadkiem użycia bind jest stworzenie bezargumentowego funktora przez zastąpienie argumentów funkcji wartościami stałymi. | + | Najprostszym przypadkiem użycia ''boost::bind'' jest stworzenie bezargumentowego funktora przez zastąpienie argumentów funkcji wartościami stałymi. |
<code cpp> | <code cpp> | ||
Linia 30: | Linia 28: | ||
</code> | </code> | ||
- | 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. | + | 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. | Znacznie ciekawszym mechanizmem jest możliwość selektywnego zastępowania argumentów lub kolejności przekazywanych argumentów. | ||
Linia 36: | Linia 34: | ||
int main() { | int main() { | ||
int i1=1,i2=2,i3=3,i4=4,i5=5,i6=6,i7=7,i8=8,i9=9; | 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 | + | 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) | + | (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 | + | //w wyniku otrzymamy ciąg: 921638457 |
- | } | + | return 0; |
- | bind(f, _2, _3, _1, _1, 0, 0, _3, 0, _2)(x, y, z); //utworzenie tymczasowego funktora i wywołanie | + | } |
- | + | ||
</code> | </code> | ||
- | 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. | + | 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ść. | + | Argumenty można dowolnie zamieniać ze stałymi jak i zamieniać ich kolejność. |
+ | |||
=====Użycie z funktorami===== | =====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). | + | ''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). |
<code cpp> | <code cpp> | ||
Linia 65: | Linia 64: | ||
</code> | </code> | ||
- | Niektóre kompilatory mają problem ze składnią w postaci bind<R>(...). Wyrażenie to można zastąpić postacią alternatwną. | + | Niektóre kompilatory mają problem ze składnią w postaci ''bind<R>(...)''. Wyrażenie to można zastąpić postacią alternatwną. |
<code cpp> | <code cpp> | ||
boost::bind(boost::type<int>(), f, _1, _1)(x); | boost::bind(boost::type<int>(), f, _1, _1)(x); | ||
</code> | </code> | ||
+ | |||
+ | |||
+ | |||
=====Użycie ze wskaźnikami do składowych===== | =====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: | + | 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: |
<code cpp> | <code cpp> | ||
Linia 86: | Linia 88: | ||
</code> | </code> | ||
- | gdzie R jest typem zwracanym przez Foo::f w przypadku metod lub typem danych w przypadku danych składowych. | + | gdzie ''R'' jest typem zwracanym przez ''Foo::f'' w przypadku metod lub typem danych w przypadku danych składowych. |
Przykład: | Przykład: | ||
Linia 106: | Linia 108: | ||
</code> | </code> | ||
- | 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(). | + | 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ę bind można też bezprolemowo użyć z funkcjami wirtualnymi. | + | Funcję ''boost::bind'' można też bezprolemowo użyć z funkcjami wirtualnymi. |
<code cpp> | <code cpp> | ||
Linia 131: | Linia 133: | ||
base* bp = &d; | base* bp = &d; | ||
- | boost::bind(&base::print, _1)(b); | + | boost::bind(&base::print, _1)(b); |
- | boost::bind(&base::print, _1)(bp); | + | boost::bind(&base::print, _1)(bp); |
- | boost::bind(&base::print, _1)(d); | + | boost::bind(&base::print, _1)(d); |
+ | |||
+ | return 0; | ||
+ | } | ||
</code> | </code> | ||
Linia 143: | Linia 148: | ||
derived! | derived! | ||
</code> | </code> | ||
+ | |||
=====Użycie zagnieżdżeń do kompozycji fukcji===== | =====Użycie zagnieżdżeń do kompozycji fukcji===== | ||
- | Część argumentów przekazywanych bind może być zagnieżdżonymi wyrażeniami bind. | + | Część argumentów przekazywanych bind może być zagnieżdżonymi wyrażeniami boost::bind. |
<code cpp> | <code cpp> | ||
Linia 152: | Linia 158: | ||
</code> | </code> | ||
- | 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)). | + | 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 bind może być bardzo dobrze zastosowana do kompozycji funkcji. | + | Ta cecha boost::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śy tego oczekiwali. | + | 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. |
<code cpp> | <code cpp> | ||
Linia 166: | Linia 172: | ||
</code> | </code> | ||
- | 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. | + | 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. |
<code cpp> | <code cpp> | ||
Linia 176: | Linia 182: | ||
</code> | </code> | ||
- | Używając bind można komponować funkcje korzystając naraz z bind i funkcji z biblioteki standardowej. | + | Używając bind można komponować funkcje korzystając naraz z boost::bind i funkcji (algorytmów) z biblioteki standardowej. |
<code cpp> | <code cpp> | ||
Linia 210: | Linia 216: | ||
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. | 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===== | + | =====Użycie 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 =. | + | 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 =. |
<code cpp> | <code cpp> | ||
Linia 222: | Linia 228: | ||
Warunkiem poprawnej kompilacji jest dokładne określenie typu szablonu obiektu function (zasada działania boost::function wykracza poza ten artykuł). | Warunkiem poprawnej kompilacji jest dokładne określenie typu szablonu obiektu function (zasada działania boost::function wykracza poza ten artykuł). | ||
- | =====Inne przykłady użycia===== | + | =====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 ==, !=, <, <=, >, >=. | ||
+ | |||
+ | <code> | ||
+ | !bind(f, ...) | ||
+ | </code> | ||
+ | |||
+ | Jest ekwiwalentem | ||
+ | |||
+ | <code cpp> | ||
+ | bind(logical_not(), bind(f, ...)) | ||
+ | </code> | ||
+ | |||
+ | gdzie logical_not jest funktorem przyjmującym jeden argument x i zwracający !x. | ||
+ | |||
+ | <code cpp> | ||
+ | bind(f, ...) op x | ||
+ | </code> | ||
+ | op jest operatorem relacji, wyrażniu temu odpowiada | ||
+ | |||
+ | <code cpp> | ||
+ | bind(relation(), bind(f, ...), x) | ||
+ | </code> | ||
+ | gdzie relation jest funktorem przyjmującym dwa argumenty a i b i zwracającym a op b. | ||
+ | |||
+ | Przeciążenie tych operatorów umożliwia na konwencjonalne negowanie wyniku boost::bind: | ||
+ | |||
+ | <code cpp> | ||
+ | std::remove_if(first, last, !bind(&X::foo, _1)); //usun obiekt nie spełniający jakiegoś warunku | ||
+ | </code> | ||
+ | |||
+ | oraz na porównywanie wyników boost::bind z wartościami: | ||
+ | |||
+ | <code cpp> | ||
+ | std::find_if(first, last, bind(&X::name, _1) == "xyz"); | ||
+ | </code> | ||
+ | |||
+ | ze znakiem zastępczym: | ||
+ | |||
+ | <code cpp> | ||
+ | bind(&X::name, _1) == _2 | ||
+ | </code> | ||
+ | |||
+ | albo z innym wyrażeniem boost::bind: | ||
+ | |||
+ | <code cpp> | ||
+ | std::sort(first, last, bind(&X::name, _1) < bind(&X::name, _2)); | ||
+ | </code> | ||
+ | |||
+ | |||
+ | |||
+ | =====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. | ||
+ | |||
+ | <code cpp> | ||
+ | #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; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <code cpp> | ||
+ | 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"))); | ||
+ | </code> | ||
+ | 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). |