Bind zastępuje predykaty!
• tech • 617 słów • 3 minuty czytania
Boost.bind to potężne narzędzie. Dzięki niemu możemy pozbyć się wielu prostych i trywialnych, a także tych trochę bardziej skomplikowanych funktorów, jakie musieliśmy definiować, bo standardowe adaptery funkcyjne z STL są ograniczone. Za niedługo zobaczymy bind
-a w standardzie, a obecnie dostępny jest w TR1.
To za jego sprawką z kodu źródłowego xime zniknie kilka predykatów. Zostaną zastąpione jedno-dwu linijkowymi złożeniami bindowych adapterów i obiektów funkcyjnych. Kod stanie się bardziej czytelny i spójny, bo nawet prosty predykat wykorzystujący jakiś element lub metodę obiektu definiowany jest jako klasa funktora, co przy kilku dodatkowych definicjach klas powoduje rozrastanie się kodu.
Może jakiś mały i ciekawy przykład z życia wzięty. Choć może nie do końca zastępujący klasę funktora predykatu, ale ukazujący implementację przydatnego uogólnionego algorytmu z wykorzystaniem kilku możliwości boost-a.
Tak się składa, że w xime i jego API niektóre obiekty będą identyfikowane po zawartości pola ID
. Typ tego elementu dla różnych typów obiektów i struktur będzie inny. Dla jednych będzie to uchwyt w postaci wskaźnika, dla innych integer, a niektóre będą identyfikowane po ciągu znakowym. Przydałaby się funkcja do przeszukiwania podanego zakresu, tablic, czy kontenerów takich obiektów właśnie po tym “identyfikującym” polu ID
.
Jak to zrobić, aby się nie narobić, czyli nie napisać od cholery kodu dla każdego typu struktur?
Da się to zrobić prosto, łatwo i generycznie. Z pomocą przychodzi nam Boost.bind i Boost.Typeof.
Implementacja takiej funkcji find_by_id
w rzeczy samej jest prostą “nakładką” na standardowy find_if
z STL-a:
template<typename Iter, typename Id>
inline Iter find_by_id(Iter first, Iter last, Id id) {
typedef typename std::iterator_traits<Iter>::value_type value_type;
typedef BOOST_TYPEOF_TPL(first->ID) ID_type;
return std::find_if(first, last,
boost::bind(std::equal_to<ID_type>(), boost::bind(&value_type::ID, _1), id));
}
Kodu samego predykatu nie trzeba tłumaczyć, jedynie dwie linijki definiujące nazwy typów mogą być zastanawiające.
Typ obiektu po jakim iterujemy value_type
jest potrzebny do określenia odpowiedniego wskaźnika do składowej ID
danego typu. Oczywiście moglibyśmy wykorzystać typ stowarzyszony iteratora (Iter::value_type
), ale niestety wtedy algorytm nie będzie współpracował z wskaźnikami podstawionymi w roli iteratorów, ba nawet się nie skompiluje. Na to rozwiązaniem jest klasa cech iterator_traits, która definiuje podstawowe typy iteratora. Dla zwykłych iteratorów przepisuje ich typy stowarzyszone, a odpowiednia specjalizacja dla wskaźników definiuje niezbędne typy.
Funktor equal_to musi być stworzony z konkretyzacji typu jaki będzie porównywał, czyli typ elementy ID
danego obiektu. Do poznania tego typu służy makro BOOST_TYPEOF, które na podstawie zmiennej jest wstanie wyznaczyć jej typ.
Odwołanie się do elementu ID
obiektu wskazywanego przez first
może wydawać się niebezpieczne w sytuacji kiedy first
będzie równy last
, czyli wskazywanie za ostatni element. Nie ma się jednak czego obawiać, bo program nie będzie próbował operować na tych danych, zmienna ta jest jedynie potrzebna w czasie kompilacji do określenia jej typu.
Przedstawiona funkcja wykorzystuje funktor equal_to
z biblioteki standardowej, który domyślnie używa operatora porównania ==
do porównania obiektów. Dla większości typów jest to jak najbardziej zalecane i da pożądany efekt. Dla ciągów znakowych trzeba dodać odpowiednią specjalizację tego szablonu, aby porównywanie “stringów” dało spodziewany efekt, a nie tylko porównywało gołe wskaźniki:
namespace std {
template<>
struct equal_to<char*> : public binary_function<char*, char*, bool> {
bool operator()(const char* left, const char* right) const {
return strcmp(left, right) == 0;
}
};
} // namespace std
Wypadałoby powyższą implementację wzbogacić o “obsługę” pustych wskaźników.
Dla typów definiowanych przez użytkownika najlepiej jest zdefiniować operator porównania albo wyspecjalizować wersję funktora equal_to
dla swojego typu.
Na koniec warto wspomnieć, że mimo iż kod ten wygląda na “ciężki” i robiący wiele dziwnych rzeczy, to w istocie zostanie przez kompilator ładnie zoptymalizowany i “zamieniony” na prostą pętelkę z jednym if-em. Kto nie wierzy może sobie samemu poeksperymentować i potestować, nie zapominając sprawdzić wygenerowany kod wynikowy ;)
Zapewne funkcja ta wyląduje w API xime lub SDK wśród innych, podobnych i przydatnych algorytmów, jakie będą wykorzystywane przez sam program i możliwe do użycia we wtyczkach.
Komentarze (0)