Czasem w czasie pisania kodu zachodzi potrzeba zgrupowania kilku obiektów w jeden. Biblioteka standardowa implementuje szablon std::pair, który potrafi zgrupować dwa obiekty. Wraz z szablonem funkcji std::make_pair i operatorami porównywania szablon ten tworzy dość przydatne programiście narzędzie. Co w momencie gdy potrzebujemy takiego narzędzia do zgrupowania 3 i więcej wartości? Możemy pokusić się o napisanie własnej klasy bądź szablonu klas grupującego obiekty. O wiele wygodniej jednak skorzystać z gotowego rozwiązania jakie oferuje nam biblioteka Boost Tuple. Oferuje ona szablon boost::tuple pozwalający agregować do 10 elementów (tworząc tzw. krotki).
Do korzystanie z typu boost::tuple niezbędne jest załączenie do programu nagłówka „boost/tuple/tuple.hpp”. Aby utworzyć obiekt typu tuple należy podać typ obiektów wchodzących w skład krotki i opcjonalnie listę wartości początkowych o typach zgodnych z typami poszczególnych obiektów tworzących krotkę.
/* Tworzymy krotkę o trzech elementach. Wszystkie zostaną zainicjowane zerami. */ boost::tuple<int, int, double> foo; /* Krotka o trzech elementach jawnie zainicjowanych. */ boost::tuple<std::string, double, int> foo("Moja krotka", 29, 5); /* Pozostałe obiekty zostaną zainicjowane domyślnie.*/ boost::tuple<std::string, double, int> foo("Moja krotka");
Jeśli jeden z elementów krotki nie udostępnia konstrukcji domyślnej konieczna jest jawna inicjalizacja.
class A //Nasza klasa A { private: A(); //Prywatny konstruktor domyślny public: A(int& i); //Publiczny konstruktor }; boost::tuple<A, A, A, A> foo; //Błąd. Brak konstruktora domyślnego dla klasy A. boost::tuple<A, A, A, A> foo(1, 2, 3, 4); //Poprawne
Do tworzenia krotek możemy też użyć funkcji make_tuple, która utworzy krotkę na podstawie dedukcji typów podobnie jak robi to funkcja make_pair dla typu std::pair z biblioteki standardowej. Domyślnie make_tuple określa typy jako niereferencyjne i modyfikowalne (brak const). Jak to zmienić przeczytasz tutaj.
boost::make_tuple(10, "Moja krotka", 10.5); //Tworzy krotkę na podstawie dedukcji typów
Ciekawym mechanizmem biblioteki Tuples jest możliwość wiązania pojedynczych wartości w krotki. Służy do tego szablon funkcji tie. Elementy krotki utworzonej przez tie są modyfikowalnymi referencjami odpowiednich wartości.
int i; char c; double d; boost::tie(i, c, d) = make_tuple(1,'a', 5.5); //Powiązanie pojedynczych wartości w krotkę std::cout << i << " " << c << " " << d; //Wynik to 1 a 5.5
Powyższy przykład prezentuje jedno z ważnych zastosowań krotek - możemy je wykorzystywać do zwracania wielu wartości z funkcji.
Jeśli w przypisaniu chcemy pominąć którąś pozycję krotki używamy obiektu ignore. Poniższy przykład pokazuje, że funkcja tie współpracuje także z obiektami klasy pair z biblioteki standardowej.
int i; /* Zmień wartość tylko i. Wartość krotki pod indeksem 0 jest ignorowana. */ boost::tie(boost::tuples::ignore, i) = std::make_pair("Krotka", 1);
Moglibyśmy po prostu pominąć wartość pod indeksem 0. Jawne ignorowanie może być jednak pomocne dla kogoś, kto w przyszłości będzie edytował nasz kod. W ten sposób od razu widać, że wartość nie jest pominięta przez przypadek.
Dostęp do elementów krotek możliwy jest dzięki metodzie get klasy tuple lub zewnętrznej wobec klasy funkcji get. Obie wymagają konkretyzacji indeksem elementu (oczywiście liczonym od zera). Obie też zwracają referencję do obiektu znajdującego się pod podanym indeksem.
boost::tuple<const int, int, int> foo(10, 20, 30); //Tworzymy krotkę o 3 elementach std::cout<<foo.get<2>(); //Wyświetli 30 std::cout<<boost::tuples::get<2>(foo); //To samo działanie z użyciem zewnętrznej funkcji const int& i=foo.get<0>(); //Dla wartości const lub krotek const zwracana jest referencja const int& j=foo.get<0>(); //Błąd kompilacji std::cout<<foo.get<5>(); //Błąd kompilacji. Rozmiar krotki 3.
Krotki można przypisywać i kopiować ale tylko jeśli krotka źródłowa i docelowa mają taką samą ilość elementów oraz jeśli elementy krotki źródłowej udostępniają konwersje na odpowiednie typy elementów krotki docelowej.
struct A //Klasa bazowa { virtual void fun(){std::cout<<"a";}; }; struct B:public A //Klasa pochodna { virtual void fun(){std::cout<<"b";}; }; boost::tuple<A> ta; //Obiekt klasy bazowej boost::tuple<B> tb; //Obiekt klasy pochodnej ta=tb; //Przypisanie poprawne. Konwersja obiektu klasy pochodnej na obiekt klasy bazowej std::cout<<ta.get<0>().fun(); //Wypisze a. Brak polimorfizmu
W skutek konwersji obiektu klasy B na obiekt klasy A utraciliśmy polimorficzne zachowanie. Rozwiązaniem problemu jest przechowywanie w krotkach wskaźników lub referencji do obiektów.
/* Hierarchia klas jak w poprzednim przykładzie. */ A a; //Tworzymy obiekt klasy A B b; //Tworzymy obiekt klasy B boost::tuple<A*> ta(&a); //Zamiast obiektu trzymamy wskaźnik do niego boost::tuple<B*> tb(&b); //Zamiast obiektu trzymamy wskaźnik do niego ta=tb; //Przypisanie std::cout<<ta.get<0>()->fun(); //Wypisze "b". Zachowaliśmy polimorfizm.
Przykład z referencjami.
/* Hierarchia klas jak w poprzednim przykładzie. */ B b; boost::tuple<A&> ta(b); std::cout<<ta.get<0>().fun(); //Wypisze "a". Zachowaliśmy polimorfizm.
Aby porównywać krotki niezbędne jest włączenie do programu nagłówka „boost/tuple/tuple_comparison.hpp”. Porównywać możemy tylko krotki o takim samym rozmiarze i jeśli dla odpowiadających elementów są zdefiniowane odpowiednie porównania. Porównanie realizowane są poprzez wywoływanie odpowiednich operatorów parami dla kolejnych obiektów pod odpowiadającymi sobie indeksami. Porównywanie kolejnych par przerywane jest gdy można już ustalić wynik porównania. Operator == zwraca true jeśli wszystkie porównania dla par zwróciły true. Operator != zwraca false gdy chodziaż jedno z porównań zwróciło false. Reszta operatorów realizuje porównania leksykograficzne.
Po włączeniu do kodu programu nagłówka „boost/tuple/tuple_io.hpp” zyskujemy dostęp do operatora pisania do strumienia ( « ) i czytania ze strumienia ( » ), a także do manipulatorów sterujących operacjami strumieniowymi. Za ich pomocą możemy odczytywać i zapisywać całe krotki na raz. Za pomocą funkcji set_open, set_close i set_delimiter możemy zmieniać odpowiednio znak początku krotki, końca krotki i znaku rozdzielającego poszczególne elementy krotki.
boost::tuple<int, double> foo; std::cout<<"Wprowadź wartość krotki w formacie (element1 element2)"<<std::endl; //Standardowy format strumienia dla krotek std::cin>>foo; //Wczytanie krotki ze strumienia wejściowego std::cout << boost::tuples::set_open('[') \ //Ustaw znak początku krotki <<boost::tuples::set_close(']') \ //Ustaw znak końca krotki <<boost::tuples::set_delimiter(':'); //Ustaw znak oddzielający elementy krotki std::cout<<foo; //Wypisze [1:2.3] zgodnie z tym co ustawiliśmy powyżej
W powyższych przykładach zawsze poprzedzaliśmy funkcje z biblioteki kwalifikatorem nazw boost lub boost::tuples. Uzyskiwaliśmy w ten sposób dostęp do funkcji z następującej hierarchii przestrzeni nazw.
namespace boost { namespace tuples { //Pełny kod biblioteki } //Funkcje dostępne bezpośrednio z przestrzeni nazw boost using tuples::tuple; using tuples::make_tuple; using tuples::tie; using tuples::get; }
make_tuple w czasie tworzenia nowych krotek domyślnie określa argumenty jako niereferencyjne i modyfikowalne. Co w momencie gdy nie jest to zachowanie pożądane? Z pomocą przychodzi nam inna biblioteka Boost - Boost Ref. Z wykorzystaniem funkcji ref możemy za pomocą make_tuple utworzyć krotkę składającą się z referencji lub referencji const ( funkcja cref).
/* Standardowe działanie. Tworzy krotkę o typie typach int, int. */ boost::tuples::make_tuple(10, 20); /* Dzięki użyciu funkcji ref tworzy krotkę int&, int. */ boost::tuples::make_tuple(boost::ref(10), 20); /*Typ pierwszego elementu to const int&. Modyfikację działania make_tuple uzyskujemy dzięki użyciu funkcji cref*/ boost::tuple<const int&, int> foo = boost::tuples::make_tuple(boost::cref(10), 20); //OK boost::tuple<int&, int> foo = boost::tuples::make_tuple(boost::cref(10), 20); //Bład kompilacji
Czasem trzymając krotki w kontenerach sekwencyjnych zachodzi potrzeba posortowania ich według jednego z elementów krotek. Możemy wtedy zdefiniować pomocniczy szablon klasy.
template<int Index> class Comparator { public: template<typename Tuple> bool operator()(const Tuple& tuple_a, const Tuple& tuple_b) const { return boost::get<Index>(tuple_a)<boost::get<Index>(tuple_b); } }
Korzystając z obiektu funkcyjnego powyższej klasy sortowanie możemy zrealizować następująco.
/* Niezbędne nagłówki. */ #include <iostream> #include <vector> #include "boost/tuple/tuple.hpp" int main(int argc, char** argv) { typedef boost::tuple<int, double, std::string> my_tuple_t; //Dla poprawienia czytelności kodu std::vector<my_tuple_t> v; //Tworzymy vector v.push_back(my_tuple_t(1, 2.0, "Pierwsza krotka")); //Dodajemy do vectora 3 różne krotki v.push_back(my_tuple_t(2, 1.0, "Druga krotka")); v.push_back(my_tuple_t(3, 3.0, "Trzecia krotka")); std::sort(v.begin(), v.end(), Comparator<1>()); //Sortujemy vector według 2 elementu krotek std::cout<<v[0].get<0>()<<v[1].get<0>()<<v[2].get<0>()<<std::endl; //OK. Wynik tej linii to 213 }
— Radosław Zegadło 2008/04/13 11:19