STL wprowadzenie Bibliografia na temat STL oraz biblioteki
STL wprowadzenie
Bibliografia na temat STL oraz biblioteki języka C++ – Nicolai M. Josuttis: C++ Standard Library: A tutorial and Reference, 1 st, Pearson 1999, przykłady: http: //www. josuttis. com/libbook/examples. zip • (PL: Nicolai M. Josuttis: C++ Biblioteka standardowa Podręcznik Programisty, Helion 2003, examples: ftp: //ftp. helion. pl/przyklady/cpbspp. zip) • Nicolai M. Josuttis: C++ Standard Library: A tutorial and Reference, 2 nd, Addison Wesley Longman 2012, (rozszerzona, uwzględnia m. in. standard C++11) – Grębosz J. : Pasja C++, RM, W-wa – The C++ Resources Network: http: //www. cplus. com/ (Obejmuje język C++ i jego biblioteki) – Inne (patrz pierwszy wykład, w tym standard języka C++)
Biblioteka języka C++ • Biblioteka języka C • Biblioteka standardowa C++ – – STL Klasy strumieniowe (patrz: wykład o bibliotece I/O) Klasy łańcuchowe (string) (patrz: wykład o klasach łańcuchowych) Miscelaneous – rozmaite dodatkowe elementy, formalnie <string> jest jednym z nich – Biblioteka zdefiniowana w przestrzeni nazw std: : – Znacząco rozbudowana od standardu C++11 (głównie część STL)
Biblioteka standardowa języka C++ • Główne składniki – Kontenery (listy, wektory, etc. ) – Iteratory (interfejs pomiędzy algorytmami a kontenerami) – Algorytmy (proste, nie bardziej skomplikowane od wyszukiwania i sortowanie) – Inne (obiekty funkcyjne, adaptory, szablony pomocnicze. . . ) • • STL to biblioteka wzorców! Dane odseparowane od metod (a co z ideą OOP? ) Zaprojektowane dla programowania wysokopoziomowego Zaprojektowane dla programowania wydajnego
BTW: rząd złożoności algorytmu • O-notacja – – Wyszukiwanie w posortowanej tablicy: O(log 2(n)) Wyszukiwanie w nieuporządkowanej tablicy: O(n) Quicksort: O(n · log 2(n)) Sortowanie bąbelkowe: O(n 2)
Kontenery • Sekwencyjne – vector (tablica dynamiczna) – dequeue (tablica dynamiczna dwukierunkowa) – list (lista dwukierunkowa) – array (C++11) – tablica o stałym rozmiarze, przekazanym jako drugi argument szablonu – forward_list (C++11) – lista jednokierunkowa – tablica (z C) i string --- nie są kontenerami STL, ale można je przetwarzać za pomocą algorytmów STL – adaptery kontenerów • stack (kolejka LIFO), queue (kolejka FIFO), priority_queue
Kontenery • Asocjacyjne – set (drzewo binarne) – multiset – map, multimap • Asocjacyjne nieuporządkowane (C++11) (zaimplementowane za pomoca tablicy mieszającej zamiast drzewa binarnego, w starszych implementacjach znane jako hash_*) – unordered_set, unordered_multiset, – unordered_map, unordered_multimap
Kontenery • vector – Zaimplementowany jako tablica dynamiczna – szybkie push_back() – szybki operator[] – wolne insert()
Kontenery • deque – Zaimplementowany jako tablica dynamiczna – szybkie push_back() (vector może być szybszy) – szybkie push_front()(brak w vector) – szybki operator[] – wolny insert()
Kontenery • list – Zaimplementowana jako lista dwukierunkowa – szybkie push_back() – szybkie push_front() – szybkie insert() – nie ma operator[] – przykład: list 1. cpp
Kontenery • forward_list – Zaimplementowana jako lista jednokierunkowa – szybkie push_front() – szybkie insert_after() – Nie ma operator[] – Nie ma push_back()
Iteratory • Iteratory zachowują się jak zwyczajne wskaźniki. . . – – – * -> ++ -== != = (vector, deque, array, string: -, <, >, +(int) ) • Ale działają dla wszystkich kontenerów! – container. begin() – container. end() – przykład: list 2. cpp // zwróć iterator do pierwszego elementu. //. . . do ostatniego
Kontenery asocjacyjne • set – Zaimplementowany jako drzewo binarne – Sortowanie przy wstawianiu (porównywanie przez domyślny operator<() ) – Odpowiednik zbiorów matematycznych – metoda find() – szybkie insert() – nie ma: push_back() – nie ma: push_front() – nie ma: operator[]
Kontenery asocjacyjne • multiset – Dozwolone powtórzenia tej samej wartości – Uporządkowanie wewnątrz grupy elementów o tych samych wartościach jest niezdefiniowane
Kontenery asocjacyjne • multimap – multiset dla par: klucz, wartość – użyj make_pair()
Kontenery asocjacyjne • map – set dla par: klucz, wartość – użyj make_pair() • Tylko dla kontenera map: operator[key]() – Indeksowanie wartością (tablica asocjacyjna) – use but not abuse
Adaptery kontenerów – stack (kolejka LIFO) – queue (kolejka FIFO) – priority_queue (kolejka priorytetowa)
stack – header <stack> – prosta koleja LIFO zaimplementowana za pomocą deque • może być oparta o kontener mający: back(), push_back(), pop_back() • wystarczy podać 2 gi argument szablonu, np. : stack<int, vector<int>> po prostu przekierowuje swoje metody do deque aby uzyskać kolejkę LIFO – example: cont/stack 1. cpp – push() – pop() – top() – – // pobierz wartość elementu ze szczytu stosu // ale nie usuwaj go size() // zwróć rozmiar stosu empty() // czy stos jest pusty? operatory : ==, !=, <, >, <=, >= // == zwraca 1 gdy rozmiary i elementy identyczne bez innych metod lub operatorów
queue – header <queue> – prosta kolejka FIFO zaimplementowana za pomocą deque • może być oparta o kontener mający : front(), back(), push_back(), pop_front() • wystarczy podać 2 gi argument szablonu, np. : queue<int, list<int>> po prostu przekierowuje swoje metody do deque aby uzyskać kolejkę FIFO – przykład: cont/queue 1. cpp – push() – pop() – back() – – – // zwróć wartość ostatnio wstawionego elementu // ale go nie usuwaj front() // zwróć wartość elementu z przodu kolejni // ale go nie usuwaj size() // zwróć rozmiar kolejki empty() // czy kolejka jest pusta? operatory: ==, !=, <, >, <=, >= bez innych metod lub operatorów
priority_queue – header <queue> – prosta kolejka priorytetowa zaimplementowana za pomocą vector • może być oparta o kontener mający: front(), push_back(), pop_front(), operator[] • wystarczy podać 2 gi argument szablonu, np. : priority_queue<int, deque<int>>, 3 ci argument to kryterium sortowania (domyślnie: operator< ) priority_queue<int, vector<int>, greater<int>> – przekierowuje swoje metody do metod kontenera vector – przykład: cont/pqueue 1. cpp – push() – pop() – top() // zwróć wartość elementu o największym priorytecie // ale go nie usuwaj z kolejki – size() // liczba elementów – empty() // czy kolejka pusta – bez innych metod lub operatorów (w tym operatora ==, etc. )
Algorytmy STL • Operują na kontenerach za pomocą iteratorów – Sposób uniwersalny, ale nie tak szybki jak metody kontenerów – Dla niektórych kombinacji algorytm/kontener wolne – Możliwe jednoczesne działanie na kontenerach różnych typów • Tylko podstawowe, proste algorytmy – Parametryzowalne przez rozmaite iteratory – Parametryzowalne przez obiekty funkcyjne i adaptory • Bardziej podatne na błędy programisty od metod kontenerów
Algorytmy STL • Przykład: algo 1. cpp – – – min_element max_element sort find reverse // operator<() // operator==() // operator=() • min_element (coll. begin(), coll. end()) [ ) – Zakres to coll. begin(), coll. end() (find 1. cpp? ) – Właściwa definicja zakresu to zadanie programisty – Koniec zakresu powinien być osiągany przez ++’owanie początku
Dygresja // bez algorytmu, pre - C++11 list<char>: : const_iterator pos; for (pos = coll. begin(); pos != coll. end(); ++pos) cout << *pos << ' '; // nadal bez algorytmu, C++11: auto i for zakresowe for (const auto & car : coll) cout << car << ' '; // algorytm i funkcja lambda - rozwiązanie preferowane for_each(coll. begin(), coll. end(), [](const auto & car) { cout << car << ' '; });
Algorytmy STL • copy (coll 1. begin(), coll 1. end(), coll 2. begin()); – Koniec zakresu podany tylko dla pierwszego zakresu – Rozmiar zakresu docelowego musi być wystarczający • Algorytm przeprowadza wyłącznie proste nadpisywanie • … bez sprawdzania rozmiaru zakresu docelowego (iteratory to interfejs do elementów kontenera, nie do kontenera) • Właściwy rozmiar kolekcji to odpowiedzialność/zadanie programisty (przykład: copy 2. cpp) • Dla niektórych kontenerów (sekwencyjne) ustawienie rozmiaru początkowego jest proste
Iteratory STL • Wielkość zakresu musi być wystarczająca, albo. . . copy (coll 1. begin(), coll 1. end(), inserter(coll 2, coll 2. begin()) ); • . . . albo użyj iteratorów wstawiających (inserter) ; ) (copy 3. cpp) – inserter dla wszystkich kontenerów, które mają metodę insert() • Wstawia przed podaną lokalizacją • Dla kontenerów asocjacyjnych lokalizacja jest tylko wskazówką – back_inserter dla kontenerów, które mają metodę push_back() – front_inserter dla kontenerów, które mają metodę push_front()
Iteratory STL • Iteratory strumieniowe – Zachowują się jak zwyczajne iteratory • Mają interfejs zwykłych iteratorów – Operują na strumieniach i/o (ioiter 1. cpp) • istream_iterator<string>(cin) – ++iter dla stream>>temp, *iter aby pobrać temp • istream_iterator<string>() – Iterator końca srtumienia (zakresu strumienia)
Iteratory STL • Iteratory odwrotne (reverse iterator) – Mają interfejs zwykłych iteratorów – Zachowują się odwrotnie (riter 1. cpp) • container. rbegin() wskazuje na ostatni element (nie pozycję po ostatnim) • container. rend() wskazuje na pozycję przed pierwszym! • ++ to --, -- to ++, itd.
Algorytmy STL • Usuwanie elementów z kontenera – Algorytm remove(. . . ) • • Tak naprawdę nie usuwa zawartości kontenera (remove 1. cpp) Nie ma dostępu do kontenera, a tylko do elementów Nie działa dla kontenerów asocjacyjnych Zwraca nowy koniec zakresu (element następny po ostatnim) – Użyj metody kontener. erase(. . . ) aby się pozbyć elementów (remove 2. cpp) • Wiele wersji • Działa dla kontenerów asocjacyjnych (remove 3. cpp)
Funkcja jako argument algorytmu • Funkcje jednoargumentowe – przykłady: foreach 1. cpp, transform 1. cpp • Predykaty – Jeden argument i wynik bool (prime 1. cpp) – Dwa argumenty i wynik bool (sort 1. cpp)
Obiekt funkcyjny jako argument algorytmu • Obiekty funkcyjne – Zachowują się jak funkcje, ale wykorzystują operator()() – przykład: foreach 2. cpp – Są obiektami • Lepiej optymalizowane przez kompilator • Mogą posiadać zmienne klasowe, „wewnętrzny stan” przekazany jako argument konstruktora (add 1. cpp) • Można mieć wiele obiektów funkcyjnych tej samej klasy
Obiekt funkcyjny jako argument algorytmu • Predefiniowane szablony obiektów funkcyjnych – less<>, greater<> set<int> s; domyślnie oznacza set<int, less<int> > s; można również: set<int, std: : greater<int> > s; – negate<>, multiply<> // do użycia w algorytmie transform(. . . )
Adaptory funkcji • Definiują przypadki szczególne użycia funkcji • Modyfikują funkcję, gdy wymagany jest inny interfejs (tj. lista argumentów) – bind 2 nd(less<int>(), 50) (fo 1. cpp) • Tworzymy domyślny drugi argument
Funkcje lambda (a. k. a. wyrażenia lambda, „lambdy”) • Lokalna, nienazwana funkcja, do użycia jako argument funkcji, szablonu (np. algorytmów STL) zamiast zwykłej funkcji lub obiektu funkcyjnego [&](int a, int b) { return v[a]. name<v[b]. name; } • Typ funkcji jest wnioskowany z wyrażenia return, można podać używając alternatywnej składni deklaracji • [] – capture list, informuje jakie zmienne z lokalnego zakresu są widoczne w ciele funkcji lambda, [] – nie widać nic (z zakresu, ale widać argumenty) [&] – wszystko, jako referencje [=] – wszystko czego używa funkcja, przekazane przez zmienną jako stała ( aby przekazywaniu przez zmienną móc ją modyfikować należy użyć „mutable” po nawiasie zamykającym listę argumentów f. lambda) [a, &c] – zmienna a przez wartość, zmienna c jako referencja • Użyj funkcji lambda, jeżeli chcesz wykonać zadanie proste i typowe, do innych zadań lepsze funkcje i obiekty funkcyjne
Funkcje lambda void f(vector<Record>& v) { vector<int> indices(v. size()); int count = 0; generate(indices. begin(), indices. end(), [&count]() { return count++; }); // sort indexes in order defined by the name field: std: : sort(indices. begin(), indices. end(), [&](int a, int b) { return v[a]. name<v[b]. name; }); //. . . }
Funkcje lambda (od C++14) • Argumenty funkcji mogą być typu auto • Argumenty w capture list mogą mieć wartości domyślne – można w ten sposób wymusić przekazanie argumentu przez rwartość/move semantics unique_ptr<int> ptr(new int(10)); auto lambda = [value = move(ptr)]{ return *value; }; – można użyć wewnątrz funkcji zmiennych niewidocznych w miejscu wywołania funkcji utworzonych w capture list (typ wyznaczony jak przez auto) auto lambda = [value = 1]{ return value; };
Wybrane inne elementy biblioteki • Klasa bitset • Inteligentne wskaźniki – – auto_ptr (removed) unique_ptr shared_ptr weak_ptr
bitset – header <bitset> – wygodna klasa do przetwarzania pól bitowych o znanym rozmiarze bitset<20> flags; – wykonywania bitowego i/o • example: cont/bitset 1. cpp – i całkowitoliczbowego i/o w formacie binarnym • example: cont/bitset 2. cpp • BTW: binary literals (C++14): 0 b 01101 0 B 01101 – bez konstruktora kopiującego i op. przypisania
bitset • bitset - konstruktory bitset<bits>: : bitset(); bitset<bits>: : bitset(unsigned long val); bitset<bits>: : bitset(const string &sc); // bez inicjalizacji char*, kilka wariantów inicjalizacji string-iem • metody i operatory klasy bitset size() // zwróć rozmiar bitset-u count() // policz jedynki any() // 1 jeżeli przynajmniej 1 bit ustawiony none() // !any() test(size_t idx) // czy bit o indeksjie idx jest ustawiony? operator==(const bitset<bits>& arg) operator!=(const bitset<bits>& arg)
bitset set() set(size_t idx, int reset() reset(size_t idx) flip(size_t idx) // ustaw wszystkie bity na jedynki value = 1) // wszystkie na 0 // bit o indeksie idx na 0 // zaneguj wszystkie bity bool operator[] (size_t idx) const // zachowanie niezdefiniowane gdy idx poza zasięgiem bitset<bits>: : reference operator[] (size_t idx) // zwróć obiekt specjalnego typu (tzw. proxy) // za pomocą którego następnie można: // =(bool), =(const reference &), flip(), operator~(), bool() bitowe operatory (rozmiary argumentów muszą pasować): ^= |= &= ^ | & ~ bitowe przesunięcia (size_t argument): <<= << >>= >> operatory strumieniowe: << >> to_ulong(), to_string()
Inteligentne wskaźniki • auto_ptr – ścisła własność wskazywanej danej, deprecated in C++11, removed in C++17 • unique_ptr – ścisła własność wskazywanej danej, od C++11 zastępuje auto_ptr • shared_ptr – współdzielony wskaźnik ze zliczaniem referencji do wskazywanego obiektu • weak_ptr – pomocniczy wskaźnik do danych zarządzanych przez shared_ptr – do przerywania pętli zależności shared_ptr
Inteligentne wskaźniki • auto_ptr – – – ścisła własność wskazywanej danej niezgodny z metodami przenoszącymi C++11 niezgodny z kontenerami i algorytmami STL Usunięty z biblioteki (removed) zamiast niego użyj unique_ptr!
Inteligentne wskaźniki • unique_ptr ścisła własność wskazywanej danej zastępuje auto_ptr sprzed C++11 jego destruktor niszczy wskazywaną daną bez konstruktora kopiującego i operatora przypisania z konstruktorem przenoszącym i operatorem przeniesienia podobnie jak auto_ptr może być użyty do tworzenia źródeł i ujść danych, unikania wycieków pamięci w przerywanych podprogramach (włączając konstruktory) – w przeciwieństwie do auto_ptr bezpieczny z algorytmami i kontenerami STL – – –
Inteligentne wskaźniki • Przykład: zwykły wskaźnik a wyjątki X* f() { X* p = new X; // tu kod mogący wyrzucić wyjątek return p; }
Inteligentne wskaźniki • Przykład: odpowiednik bezpieczny w przypadku wyjątku X* f() { unique_ptr<X> p(new X); // albo {new X} ale nie = new X // tu kod mogący wyrzucić wyjątek return p. release(); }
Inteligentne wskaźniki • Przykład: źródło danych, bezpieczne w przypadku wyjątku unique_ptr<X> f() { unique_ptr<X> p(new X); // albo {new X} ale nie = new X // tu kod mogący wyrzucić wyjątek return p; // własność jest przenoszona poza f() }
Inteligentne wskaźniki • shared_ptr – współdzielony wskaźnik ze zliczaniem referencji do wskazywanego obiektu. . . –. . . oraz wspólną własnością obiektu przez wszystkie shared_ptr odnoszące się do niego. . . –. . . i poprawnym działaniem w środowisku wielowątkowym – tworzone wskaźniki shared_ptr do tej samej danej są zliczane w sposób szybki i prosty • nowy wskaźnik do określonej danej jest tworzony za pomocą innego wskaźnika na tę daną, który już istnieje (przykład dalej) – przy destrukcji shared_ptr licznik referencji jest dekrementowany • gdy osiągnie 0 wskazywana dana jest destruowana
Inteligentne wskaźniki void test() { shared_ptr<int> p 1(new int); // licznik wynosi 1 { shared_ptr<int> p 2(p 1); // licznik wynosi 2 { shared_ptr<int> p 3(p 1); // licznik wynosi 3 // licznik powraca do 2 } // licznik powraca do 1 } // licznik staje sie 0 i int jest zwalniany }
Inteligentne wskaźniki • shared_ptr wg. Stroustroup’a nie jest uniwersalnym zastępcą zwykłego wskaźnika, ze względu na poniższe koszty: – cykliczna struktura zbudowana z shared_ptr spowoduje wyciek przy destrukcji (któremu, kosztem dodatkowych operacji, można przeciwdziałać, np. używając weak_ptr), – "shared ownership objects" zazwyczaj pozostają nieusunięte dłużej, niż obiekty lokalne, powodując zwiększone zużycie zasobów, – mogą być kosztowne w środowisku wielowątkowym (ze względu na koszt unikania wyścigu danych dla licznika referencji), – destruktor współdzielonego obiektu nie wykona się w przewidywalnym momencie, oraz – łatwiej popełnić błąd w algorytmie/logice aktualizacji współdzielonego obiektu, niż dla posiadanego jednokrotnie.
Inteligentne wskaźniki • weak_ptr – pomocniczy wskaźnik do danych zarządzanych przez shared_ptr; nie posiada własności danych, przeznaczony dla danych posiadanych przez shared_ptr – dostarcza testu czy wskazywana dana istnieje – posiada mechanizm blokowania/zapewnienia że na czas operacji wskazywana dana nie zostanie usunięta – typowo nie niszczy wskazywanej danej • chyba że destrukcja została odłożona ze względu na blokowanie (ostatni shared_ptr do danej został usunięty gdy dana była zablokowana), wtedy destrukcja po odblokowaniu
- Slides: 49