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

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/