C++ - szablony Tworzenie programów, które w wyniku działania dostarczają kodów źródłowych (innych programów). Narzędziem jest preprocesor oraz mechanizm szablonów: dostarczają instrukcji pozwalających na manipulowanie kodem źródłowym. Algorytm jest wykonywany w trakcie kompilacji. 180 template <unsigned n> struct Silnia { static const unsigned value = n*silnia<n-1>::value; template<> struct Silnia<0> { // specjalizacja jawna static const unsigned value = 1; int main() { int i = Silnia<5>::value; cout << i; return 0; Tworzenie metaprogramów może dawać mało czytelny kod (czasami). Używanie metaprogramów daje z reguły bardziej czytelny i elastyczny kod. Metaprogramy wykorzystują jedynie byty dostępne w czasie kompilacji (stałe całkowite, typy, itd.), dlatego rekurencja jest powszechna w tych programach (nie można tworzyć zmiennych ani pętli). 181 182 template <unsigned n> inline double pow(double x) { return x* pow<n-1>(x); template<> inline double pow<0>(double x) { return 1.0; int main() { double b = 3.14; double a = pow<3>(b); cout << a; return 0; Szablon pow : skraca zapis, zwiększa czytelność kodu oraz jego szybkość. Ale.. Jeżeli wykładnik jest dużą liczbą całkowitą, to utworzona przez szablon pow funkcja jest długa, wielkość kodu wynikowego wzrasta. Ulepszenie: w szablonach można implementować złożenia funkcji. Np. x n dla n=2 m można wykonać jako m-krotne podnoszenie do kwadratu. Dla n=1024 to daje (tylko) 10 operacji podnoszenia do kwadratu. 183 184 1
Potęga dowolnej liczby całkowitej dodatniej - pomysł: 1. Zapisać wykładnik n binarnie, np.: n=19, to: B = [0001 0011] gdzie n = b m 2 m + b m-1 2 m-1 + b m-2 2 m-2 +.. b 0 2 0 2. Stąd dla przykładowej wartości 19: n = b 4 2 4 + b 1 2 1 + b 0 2 0 3. Wtedy x n jest iloczynem składników x^(2 m ), np.: x 19 = x 16+2+1 tj.: (((x 2 ) 2 ) 2 ) 2 *x 2 *x 1 4... a tego właśnie potrzebowaliśmy. template <unsigned n> double Power(double x) { return Power<2>(Power<n/2>(x)) * Power<n%2>(x); template<> double Power<2>(double x) { return x*x; template<> double Power<1>(double x) { return x; template<> double Power<0>(double x) { return 1; /// double c = Power<19>(b); n=19: P<2>(P<9>(x)) P<1>(x) n=9: P<2>(P<4>(x)) P<1>(x) n=4: P<2>(P<2>(x)) P<2>(x) = x 2 P<1>(x) = x Stąd: (((x 2 ) 2 ) 2 x) 2 x X 19 = x 16 x 2 x 1 185 186 Metaprogramy mogą dostarczać przybliżenia wartości funkcji matematycznych z założoną dokładnością. funkcja wykładnicza e x = 1 + 1! + 2! + 3! +, < < tzw. szereg Taylora. Funkcja wykładnicza z zadaną dokładnością przykład: template <unsigned int N> inline double Exp(double x) { return Exp<N-1>(x)+Power<N>(x)/Silnia<N>::value; template <> inline double Exp<0>(double x) { return 1.0; // double d = Exp<5>(a); Możemy dostarczyć szablonu, który będzie generował wyrażenie zawierające początkowe wyrazy tego szeregu. 187 188 Kolekcje typów Kolekcja pozwala opisać zestaw typów, dla których będziemy wykonywać jakieś przetwarzanie (np. generować metodę). Przykład kolekcji: template <class H, class T> struct TList { typedef H Head; typedef T Tail; struct NullType { Lista przechowująca trzy typy: short, int oraz long: typedef TList<short, TList<int, TList<long, NullType>>> Integers; 189 Tworzenie list za pomocą Tlist nie jest wygodne: należy rekurencyjnie zagłębiać szablony. Rozszerzenie tej konstrukcji: template <class T0, class T1, class T2> struct TListCreator { typedef TList< T0, typename TListCreator<T1,T2,NullType>::type> type; template <> struct TListCreator<NullType, NullType, NullType> { typedef NullType type; typedef TListCreator <short, int, long>::type Integers2; Wynikiem rozszerzenia jest type: przechowuje listę typów. Szablon z przykładu powyżej pozwala na listę od 0 do 3 elementów. 190 2
Dla kolekcji typów dostarcza się operacji, które pozwalają wykorzystywać te kolekcje w programach. Aby ustalić rozmiar listy typów: template <class TL> struct Size { static const int value = Size <typename TL::Tail>::value+1; template <> struct Size<NullType> { static const int value = 0; int k = Size<Integers2>::value; template <class H, class T> struct TList { typedef H Head; typedef T Tail; Dla kolekcji typów dostarcza się operacji, które pozwalają wykorzystywać te kolekcje w programach. Aby uzyskać typ dla danego indeksu: template <class TL, int n> struct GetTypeAt { typedef typename GetTypeAt<typename TL::Tail, n-1>::type type; template <int n> struct GetTypeAt<NullType, n> { typedef NullType type; // koniec, gdy koniec listy template <class TL> struct GetTypeAt<TL, 0> { typedef typename TL::Head type; // koniec, gdy właściwy typ // GetTypeAt<Integers2,1>::type k = Size<Integers2>::value; // int 191 192 C++11 Lista rozszerzeń C++11 obecnych w VC2010: C++ 11 wybrane elementy 1. Referencje do rvalue, 2. Pętla for oparta na zakresie, 3. Dedukcja typów danych, 4. Sprytne wskaźniki, 5. nullptr 6. Kontenery mieszające (kontenery z haszowaniem). 194 C++11: referencje do rvalue C++ 11: Referencje do rvalue lvalue i rvalue lvalue - wyrażenie które odnosi się do obszaru pamięci i pozwala na pozyskanie adresu tego obszaru za pomocą operatora & rvalue - wyrażenie nie będące lvalue: int foobar(); int j = 0; j = foobar(); // ok, foobar() jest rvalue int* p2 = &foobar(); // błąd, nie można pobrać adresu j = 42; // ok, 42 jest rvalue 196 3
C++11: referencje do rvalue Przykładowy problem Przyjmijmy, że mamy klasę w której jedno z pól jest wskaźnikiem m_presource np. do pewnego kontenera. Przeciążony operator kopiowania dla tej klasy: X& X::operator=(X const & rhs) { // [...] // zniszcz zasób wskazywany przez m_presource // sklonuj to, na co wskazuje rhs.m_presource // dołącz klon do m_presource. // [...] Teraz można wywołać kod: X foo(); X x; x = foo(); // wywołanie przeciążonego operatora przypisania C++11: referencje do rvalue Przykładowy problem (move semantics) Zdecydowanie mniejszy nakład pracy dałby taki przeciążony operator: // [...] // swap ( m_presource, rhs.m_presource ) // [...] To jest właśnie move semantics. Żeby to zadziałało, potrzebujemy, aby argument operatora był referencją. Jeżeli argument jest lvalue nie ma problemu. A jeżeli argument jest rvalue?.... wtedy przydałby się drugi referencyjny typ danych, który byłby wypierany, kiedy argumentem jest rvalue. 197 198 C++11: referencje do rvalue C++11: referencje do rvalue Przykładowy problem (move semantics) Rozwiązanie: Przykładowy problem (move semantics) Rozwiązanie dla przeciążonego operatora przypisania: void foo(x& x) {.. void foo(x&& x) {.. X x; X foobar() {.. ; return..; // referencja do lvalue // referencja do rvalue foo(x); // argument jest lvalue: wywołuje foo(x&) foo(foobar()); // argument jest rvalue: wywołuje foo(x&&) Na etapie kompilacji rozstrzyga się, jaki argument zostanie podany w wywołaniu. X& X::operator=(X const & rhs); // klasyczna implementacja X& X::operator=(X&& rhs) { // Move semantics: zamiana zawartości pomiędzy this i rhs //.. return *this; Uwaga: tak, to prawda, że && można stosować w dowolnej funkcji, ale przyjmuje się stosowanie wyłącznie do przeciążonych operatorów przypisania i konstruktorów kopiujących. 199 200 C++11: referencje do rvalue Move semantics Czy zmienna referencyjna do rvalue jest traktowana jako rvalue? void foo(x&& x) { X anotherx = x; // który operator przypisania zadziała? //... (x jest referencją do rvalue, więc powinien operator=(x&& x)..) Nie. Rzeczy zadeklarowane jako rvalue mogą być lvalue lub rvalue. Prosta zasada mówi: jeżeli coś ma nazwę, to jest lvalue. W przeciwnym razie jest rvalue. C++ 11: Pętla for oparta na zakresie 201 4
C++11: pętla for oparta na zakresie Nowa składnia pętli for for ( deklaracja : wyrażenie ) instrukcje wyrażenie tablica, obiekt implementujący semantykę kontenera (zwraca obiekt w stylu iteratora za pomocą metod begin i end) deklaracja zmiennej - typu takiego jak elementy w tablicy lub kontenerze instrukcje wykonywane tyle razy, ile elementów przechowuje tablica lub kontener vector<int> vec; vec.push_back( 10 ); vec.push_back( 20 ); C++11: pętla for oparta na zakresie Przykłady Modyfikowanie zawartości kontenera: vector<int> vec; vec.push_back( 1 ); vec.push_back( 2 ); for (int& i : vec ) { i++; // inkrementuje wartości z wektora for (int i : vec ) { cout << i; UKSW, WMP. SNS, Warszawa 203 204 C++11: pętla for oparta na zakresie Obiekty posiadające zakres to: wszelkie kontenery z biblioteki STL, ponieważ: 1. posiadają iteratory, które wspierają metody operator*, operator!= i operator++ (zadeklarowane jako metody własne albo jako funkcje globalne), 2. posiadają metody begin i end (zadeklarowane jako metody własne albo jako funkcje globalne), zwracające iteratory na początek i koniec kontenera. wszelkie inne obiekty zaimplementowane samemu o funkcjonalności kontenerów STL (obowiązkowa w/w funkcjonalność). C++ 11: Dedukcja typów danych 205 Mechanizmy dedukcji typów danych Pochodzący C++98: 1. Szablony i ich parametry Wprowadzone w C++11: 1. auto 2. decltype Pozwalają uniknąć ciągłego wypisywania typów, które są oczywiste bądź powtarzają się. Zmiana typu zmiennej w jednym miejscu kodu propaguje się na cały kod (kompilator sam dedukuje nowy typ danych uwaga: może nas zaskoczyć). 207 Zasady dedukcji dla auto Podobne do zasad dedukcji dla parametrów szablonu z C++98 Deklarując auto jako typ zmiennej auto x = 10; auto odgrywa rolę taką, jak parametr T w szablonie: template <typename T> void fun(t x) ; fun(10); Stąd: const char name[] = "ABC"; // name: const char[13] auto arr1 = name; // arr1: const char* void Fun(int, double); auto func1 = somefunc; // Fun: void(int, double) // func1: void (*)(int, double) 208 5
Pożytek z auto Mamy szablon funkcji, która na pewnym zakresie elementów kontenera identyfikowanym przez parę iteratorów, wykonuje czynności ważne i potrzebne : template<typename It> // wykonuje czynności ważne i potrzebne void czwip(it b, It e) // na rzecz elementów od 'b' do 'e' { for (; b!= e; ++b) { typename std::iterator_traits<it>::value_type currvalue = *b;... Odwołujemy się pracowicie do cech charakterystycznych iteratora, żeby pozyskać typ danych wskazywanych przez te iteratory. Pożytek z auto Mamy szablon funkcji, która na pewnym zakresie elementów kontenera identyfikowanym przez parę iteratorów, wykonuje czynności ważne i potrzebne : template<typename It> // wykonuje czynności ważne i potrzebne void czwip(it b, It e) // na rzecz elementów od 'b' do 'e' { for (; b!= e; ++b) { auto currvalue = *b;... Wersja dla mniej pracowitych. 209 210 Pożytek z auto Jeszcze jeden przykład dla mniej pracowitych : map<string, string> address_book; for ( auto address_entry : address_book ) { cout << address_entry.first << " < " << address_entry.second << ">" << endl; Pożytek z auto Pozyskanie rozmiaru kontenera: std::vector<int> v;... unsigned sz = v.size(); Co prawda typ zwracany przez.size() to: vector<int>::size_type ale wszyscy wiedzą, że to typ całkowity bez znaku, więc spotyka się unsigned. Nie wszyscy jednak wiedzą, że choć w Win32 zmienne tych dwóch typów zajmują te samą liczbę bajtów, to już w Win64 nie (unsigned zajmuje mniej).. Rekompilacja kodu na Win64 powiedzie się, ale dla dostatecznie dużych zbiorów danych działanie może przestać być poprawne. Dlatego prościej i bezpieczniej pisać: auto sz = v.size(); 211 212 Niespodzianki z auto mamy funkcję, która dla widgetów zwraca wektor opisujący ich cechy, tj. co potrafią, a czego nie: std::vector<bool> cechy(const Widget& w); Kod programu: Widget w;... bool cecha5 = cechy(w)[5]; // czy posiada cechę nr 5.. A gdyby tak napisać: auto cecha5 = cechy(w)[5]; // czy posiada cechę nr 5 Niespodzianki z auto Dla: auto cecha5 = cechy(w)[5]; Typ zmiennej cecha5 to vector<bool>::reference (klasa zagnieżdżona w vector<bool>). Powód jest natury technicznej: klasa vector<bool> została tak zaprojektowana, aby przechowywać wartości w postaci spakowanej jeden bit na każdy element (a w C++ nie ma referencji do pojedynczych bitów). Stąd operator[] dla vector<bool> zwraca obiekt, który działa tak jak bool& Oczywiście, wśród jego wielu funkcjonalności, istnieje też możliwość konwersji tego obiektu do bool. Powinno działać tak samo. 213 214 6
Niespodzianki z auto Dla: auto cecha5 = cechy(w)[5]; Typ zmiennej cecha5 to vector<bool>::reference (klasa zagnieżdżona w vector<bool>). Teraz zmienna cecha5 jest kopią obiektu przechowującego referencję do właściwej informacji bitu nr 5. Niestety, funkcja cechy zwraca referencja do obiektu, który za chwilę zniknie (funkcja cechy zwraca kontener obiekt tymczasowy, więc jego elementy też są tymczasowe..). Dlatego cecha5 będzie przechowywać referencję do obiektu, który nie istnieje. Niespodzianki z auto Klasa: vector<bool>::reference to tzw. klasa proxy (proxy class). Takich klas jest wiele. Przestroga: należy unikać sytuacji w kodzie, gdzie: auto pewnazmienna = wyrażenie typu klasa proxy ; No tak, tylko że one zostały tak napisane, żeby być jak najmniej widoczne (w założeniu: mają być całkiem niewidoczne). Aby się nie dać oszukać, należy: sprawdzać nagłówki funkcji i metod (np. w źródłach bibliotek), albo.. auto cecha5 = static_cast<bool>(cechy(w)[5]); 215 216 Dedukcja typu przez decltype zwraca typ zmiennej lub wyrażenia podanego w argumencie: const int i = 0; bool f(const Widget& w); // decltype(i): const int // decltype(w): const Widget& // decltype(f): bool(const Widget&) struct Point { int x, y; // decltype(point::x): int // decltype(point::y): int Widget w; // decltype(w): Widget Dedukcja typu przez decltype Przydaje się szczególnie kiedy szablon funkcji ma zwrócić typ danych zależny od typu parametru (ale nie będący typem parametru) funkcja zwracająca element z kontenera po uprzedniej autoryzacji dostępu do danych dla bieżącego użytkownika: template<typename Container, typename Index> (?) authandaccess(container& c, Index i) { Funkcja powinna zwracać typ taki, jak ma c[i] if (f(w)) // decltype(f(w)): bool Jak go pozyskać? 217 218 Dedukcja typu przez decltype Dygresja: W C++11 wprowadzono możliwość podania typu zwracanego przez funkcję w miejscu tuż za jej nagłówkiem (tzw. trailing return type); bardzo przydatne, kiedy typ zwracanych danych ma skomplikowany zapis, np. : auto fpif(int) -> int(*)(int) Drugie zastosowanie trt kiedy zwracany typ zależy argumentów wywołania: template<typename Container, typename Index> auto authandaccess(container& c, Index i) -> decltype(c[i]) { authenticateuser(); // autoryzacja dostępu return c[i]; // dostęp do danych C++ 11: Sprytne wskaźniki 219 7
Kontrola zmiennych dynamicznych TR1 dostarcza pary szablonów: 1. shared_ptr 2. weak_ptr Ich zadanie: zaalokowane zasoby są dostępne tak długo, póki są potrzebne, ale nie dłużej. Kontrola zmiennych dynamicznych Obiekt typu shared_ptr<ty> Przechowuje wskaźnik do obiektu typu Ty. Jeżeli wskaźnik jest różny od NULL, do zaalokowanego zasobu mamy dostęp operatorami: operator-> oraz operator*. Obiekt typu shared_ptr<ty> przechowuje też licznik odwołań do zasobu. Kiedy kopiujemy obiekt, licznik jest inkrementowany. Kiedy usuwamy kopie, licznik jest dekrementowany. Kiedy licznik schodzi do zera, zasób jest zwalniany. 221 222 Kontrola zmiennych dynamicznych Obiekt typu weak_ptr<ty> Przechowuje wskaźnik do obiektu typu Ty. Jeżeli wskaźnik jest różny od NULL, do zaalokowanego zasobu mamy dostęp operatorami: operator-> oraz operator*. Obiekt typu weak_ptr<ty> nie przechowuje licznika odwołań do zasobu. Mimo to, też się bardzo przydaje. 223 Kontrola zmiennych dynamicznych shared_ptr posiada zasób, jeżeli: 1. Został utworzony z argumentem zawierającym wskaźnik do zasobu, 2. Został utworzony jako kopia obiektu posiadającego ten zasób, 3. Został utworzony z obiektu weak_ptr, który wskazywał na ten zasób, 4. Posiadanie zostało mu przypisane w wyniku użycia operator= lub reset. W tym przypadku przestaje posiadać poprzedni zasób, o ile taki posiadał. weak_ptr wskazuje na zasób, jeżeli: 1. Został utworzony z obiektu shared_ptr, który wskazywał na ten zasób, 2. Został utworzony z obiektu weak_ptr, który wskazywał na ten zasób, 3. Posiadanie zostało mu przypisane w wyniku użycia operator=. W tym przypadku przestaje posiadać poprzedni zasób, o ile taki posiadał. 224 Kontrola zmiennych dynamicznych Obiektowi można przypisać kontrolę nad zasobem lub uczynić pustym. Obiekt traci kontrolę nad zasobem, kiedy przypisywana mu jest kontrola nad innym zasobem, albo kiedy jest usuwany. Kiedy ostatni obiekt typu shared_ptr, posiadający pewien zasób, jest usuwany, zasób jest zwalniany. Jeżeli się tak stanie, wszystkie obiekty typu weak_ptr, które wskazywały na ten zasób, tracą ważność (expire). Kontrolowany zasób ma swój usuwacz (has a deleter) jeżeli jego oryginalny właściciel otrzymał ten zasób za pomocą funkcji wywołanej ze wskaźnikiem na zasób oraz na obiekt usuwacza. Kontrola zmiennych dynamicznych przykłady: Deklaracja obiektu reprezentującego sprytny wskaźnik: #include <memory> struct resource { // przykładowa struktura do alokacji resource(int i0 = 0) : i(i0) { int i; int main() { shared_ptr<int> sp; // konstruktor domyślny daje pusty obiekt shared_ptr<resource> sp(new resource(3)); 225 226 8
Kontrola zmiennych dynamicznych przykłady: Dostarczenie usuwacza: struct deleter { void operator()(resource *res) { cout << "destroying resource at " << (void*)res << '\n'; delete res; int main() { shared_ptr<resource> sp(new resource(3), deleter()); Kontrola zmiennych dynamicznych przykłady: Kopiowanie: Uwaga: nie należy używać tego samego zasobu przy tworzeniu dwóch różnych sprytnych wskaźników, bo destruktor też będzie użyty dwa razy raczej: jeden utworzyć j.w., drugi za pomocą konstruktora kopiującego z tego pierwszego. shared_ptr<resource> sp0(new resource(4)); // sp0 przechowuje wskaźnik na zasób shared_ptr<resource> sp1(sp0); // sp1 udostępnia ten sam zasób 227 228 Kontrola zmiennych dynamicznych przykłady: Dostęp do zasobu: int *ip = new int(3); // alokowanie zasobu typu int cout << (void*)ip << '\n'; // pokazanie adresu shared_ptr<int> sp(ip); // tworzenie obiektu shared_ptr cout << (void*)sp.get() << '\n'; // pokazanie przechowywanego adresu cout << *sp << '\n'; cout << (void*)&*sp << '\n'; // pokazanie przechowywanej wartości // pokazanie adresu wartości Kontrola zmiennych dynamicznych przykłady: Dostęp do pól zasobu typu strukturalnego: struct S { int member; int main() { S *s = new S; // tworzenie obiektu typu S s->member = 4; // przypisanie do member shared_ptr<s> sp(s); // tworzenie obiektu shared_ptr if (sp) // konwersja na bool (przydatna!) cout << sp->member << '\n'; // pokazanie wartości member 229 230 Kontrola zmiennych dynamicznych przykłady: Liczenie sprytnych wskaźników odnoszących się do jednego zasobu: spi sp0; // pusty obiekt cout << "empty object: " << sp0.use_count() << '\n'; spi sp1((int*)0); // żadnego zasobu, ale niepusty cout << "null pointer: " << sp1.use_count() << '\n'; spi sp2(new int); // posiada zasób cout << sp2.unique() << '\n'; // zwraca true jedyny właściciel cout << "one object: " << sp2.use_count() << '\n'; { // tworzymy krótko żyjący obiekt sp3 spi sp3(sp2); // kopiowanie cout << "two objects: " << sp2.use_count() << '\n'; cout << sp2.unique() << '\n'; // false - dwóch właścicieli // sp3 usunięty cout << "one object: " << sp2.use_count() << '\n'; 231 Kontrola zmiennych dynamicznych przykłady: Przekazywanie i zwalnianie zasobów: sps sp0(new resource(1)); // alokacja i przypisanie zasobu sps sp1(new resource(2)); // alokacja i przypisanie zasobu sps sp2(sp0); // przypisanie zasobu sp1 = sp0; // przypisanie zasobu i zwolnienie zasobu sp2.reset(); // zwolnienie zasobu sp0.swap(sp2); // zamiana przypisań zasobów if(sp0 == sp1) cout << "Nie jest dobrze." if(sp0!= sp1) cout << "Jest dobrze." 232 9
#include <algorithm> #include <memory> #include <iostream> #include <set> using namespace std; Kontrola zmiennych dynamicznych przykłady: Operator porównania mniejszy-niż i zastosowanie z kontenerem: typedef shared_ptr<int> spi; typedef set<spi> iset; typedef iset::const_iterator citer; static void lookup(const iset& data, spi sp) { // szuka obiektu pasującego do sp citer res = lower_bound(data.begin(), data.end(), sp); cout << *sp; if (res == data.end() *res!= sp) cout << " nie ma.\n"; else cout << " jest.\n"; Kontrola zmiennych dynamicznych przykłady: Operator porównania mniejszy-niż i zastosowanie z kontenerem: iset data; // kontener (pusty) spi sp0(new int(0)); spi sp1(new int(1)); spi sp2(new int(2)); spi sp3(sp1); // dzielenie własności z sp1 spi sp4(new int(1)); // taka sama wartość jak sp1, ale inny zasób data.insert(sp0); data.insert(sp1); data.insert(sp2); lookup(data, sp1); // szukamy sp1 - sukces lookup(data, sp3); // szukamy sp3 - sukces lookup(data, sp4); // szukamy sp4 -? 233 234 Kontrola zmiennych dynamicznych weak_ptr: Obiekty tego typu przydatne są do przerywania cykli w strukturach danych. Cykl występuje gdy dwa lub więcej kontrolowanych zasobów przechowują wskaźniki na siebie nawzajem, tak że tworzą one pętlę. Np. jeżeli element head zawiera pole typu shared_ptr, który wskazuje na inny element N1, który zawiera pole shared_ptr, wskazujące na head, to takie dwa elementy tworzą pętlę. Ponieważ takie dwa elementy wskazują na siebie nawzajem, żaden nie będzie miał nigdy licznika wskazań o wartości zero. Dlatego nigdy nie zostaną usunięte, nawet, jeżeli nikt na nich nie będzie już wskazywał. Aby to zmienić N1 powinien zawierać pole typu weak_ptr, wskazujące na head. 235 Kontrola zmiennych dynamicznych weak_ptr: Jeżeli N1 zawiera pole typu weak_ptr, wskazujące na head, to: 1. Nadal można sięgnąć do head z N1. 2. Kiedy ostatni shared_ptr wskazujący na head zostanie usunięty, licznik wskazań na head będzie równy zero i head zostanie usunięty. 3. Destruktor head usunie również shared_ptr wskazujący na N1 wtedy licznik wskazań na N1 będzie równy zero i N1 też zostanie usunięty. struct node { // struct do demonstracji cykli shared_ptr<node> next; weak_ptr<node> weak_next; 236 Kontrola zmiennych dynamicznych weak_ptr: Przykład struktury danych z cyklem i bez cyklu: node *head = new node; node *N1 = new node; shared_ptr<node> root(head); head->next = shared_ptr<node>(n1); N1->next = root; N1->weak_next = root; // cykl! // nie ma cyklu! Kontrola zmiennych dynamicznych weak_ptr: Przykład utraty ważności wskaźnika: cout << boolalpha; shared_ptr<resource> sp(new resource); weak_ptr<resource> wp(sp); cout << "wskazuje na zasób: " << wp.expired() << '\n'; cout << "liczba posiadaczy zasobu: " << wp.use_count() << '\n'; sp.reset(); cout << "stracił ważność: " << wp.expired() << '\n'; 237 238 10
C++11: nullptr Zerowanie wskaźnika Do wyzerowania wskaźnika posługujemy się NULL bądź 0. C++ 11: nullptr To są liczby. Stąd może pojawić się niejednoznaczność: void fun(int); // trzy przeciążenia fun void fun(bool); void fun(void*); f(0); f(null); Dlatego w C++11 wprowadzony został nullptr. // wywoła f(int), nie f(void*) // wywoła f(int), nigdy f(void*) 240 C++11: nullptr Zerowanie wskaźnika nullptr nie jest typem całkowitoliczbowym. Działa, jakby był wskaźnikiem na każdy dowolny typ (taka magia..) C++11: nullptr Zerowanie wskaźnika nullptr nie jest typem całkowitoliczbowym. Działa, jakby był wskaźnikiem na każdy dowolny typ (taka magia..) f(nullptr); // wywoła f(void*) f(nullptr); // wywoła f(void*) Pomaga ujednoznacznić kod: auto result = findrecord( /* argumenty */ ); if (result == 0) {... Jakiego typu będzie result? Pomaga ujednoznacznić kod: auto result = findrecord( /* argumenty */ ); if (result == nullptr) {... Jakiego typu będzie result? 241 242 C++11: nullptr C++11: nullptr class Widget { auto result1 = Call(f1, 0); // 'int (Widget *)' : cannot convert parameter 1 from 'int' to 'Widget *' int f1(widget* pw) { return 0; double f2(widget* pw) { return 0.0; bool f3(widget* pw) { return true; template<typename FuncType, typename PtrType> auto Call(FuncType func, PtrType ptr) -> decltype(func(ptr)) { return func(ptr); auto result2 = Call(f2, NULL); // 'double (Widget *)' : cannot convert parameter 1 from 'int' to 'Widget * auto result3 = Call(f3, nullptr); // działa! 243 244 11
C++ 11: Wyrażenia lambda Obiekty wywoływalne (callables): W C++98 predykaty logiczne i funktory arytmetyczne, przekazywane jako jeden z argumentów przy wywołaniu algorytmu, można było tworzyć na podstawie: 1. Funkcji (lub wskaźników do funkcji) o ustalonej, pasującej do potrzeb algorytmu liczbie argumentów 2. Obiektów klas wywoływalnych (z przeciążonym operatorem wywołania obiektu) W C++11 dochodzą jeszcze wyrażenia lambda: - Wywoływalny kawałek kodu (funkcja inline, bez własnej nazwy) - Mają zdefiniowany typ zwracanej wartości, argumenty wywołania i kod wykonywalny, można je definiować wewnątrz innych funkcji. 246 Wyrażenie lambda budowa: [capture list] (parameter list) -> return type { function body gdzie: capture list lista lokalnych zmiennych zadeklarowanych w otoczeniu parameter list lista parametrów wywołania return type typ zwracanych danych function body wykonywalny kod funkcji Uwaga: typ zwracanych danych jest deklarowany w miejscu tuż za nagłówkiem z wykorzystaniem strzałki -> (tj. wg notacji trailing return type); 247 Wyrażenie lambda budowa: [capture list] (parameter list) -> return type { function body Można pominąć listę parametrów oraz informację o typie zwracanych danych, ale muszą wystąpić: lista lokalnych zmiennych oraz kod wykonywalny, np.: auto f = [] { return 42; f obiekt wywoływalny, który nie przyjmuje żadnych argumentów i zwraca 42. Wywołanie jest identyczne jak w przypadku funkcji: cout << f() << endl; // wypisuje w oknie konsoli 42 Pominięcie informacji o typie zwracanych danych: typ zwracany to void, lub jeżeli kod zawiera tylko return, to typ jest dedukowany na tej podstawie. 248 Wyrażenie lambda z argumentami: porównanie długości dwóch napisów [](const string &a, const string &b) { return a.size() < b.size(); Argumentom nie można przypisywać wartości domyślnych. Przykład wykorzystania wywołanie algorytmu sortującego słowa w kontenerze words, z wykorzystaniem predykatu zdefiniowanego w postaci lambdy : stable_sort(words.begin(), words.end(), [](const string &a, const string &b) { return a.size() < b.size();); Przechwytywane zmienne lista: [ ] lista przechwyconych wartości jest pusta, kod wyrażenia lambda używa tylko tego, co zostało przekazane w argumentach wywołania [nazwa1, &nazwa2,..] lista zmiennych lokalnych przechwyconych domyślnie przez wartość; jeżeli przed nazwą stoi symbol &, to następuje przechwycenie przez referencję 249 250 12
Wyrażenie lambda z listą lokalnych zmiennych przechwyconych przez wartość: sprawdzenie, czy długość jest większa od wartości przechowywanej w zmiennej sz: [sz](const string &a) { return a.size() >= sz; W kodzie wyrażenia lambda można odwoływać się tylko do tych zmiennych dostępnych lokalnie w otoczeniu wyrażenia, które zostały zadeklarowane w liście lokalnych zmiennych. Przykład wykorzystania znalezienie w kontenerze words pierwszego słowa o długości równej lub większej niż sz: auto wc = find_if(words.begin(), words.end(), [sz](const string &a) { return a.size() >= sz; ); 251 Wyrażenie lambda i zmienne globalne: Po znalezieniu pierwszego słowa o odpowiedniej długości, możemy następnie wypisać wszystkie słowa, które po nim występują: for_each(wc, words.end(), [](const string &s){cout << s << " ";); cout << endl; Uwaga: mimo, że lista zmiennych lokalnych jest pusta, kod korzysta z cout (?) 1. Lista lokalnych zmiennych służy do przekazania do kodu wyrażenia wyłącznie zmiennych lokalnych non-static. 2. Zmienne zdefiniowane poza funkcją, gdzie wywołana została lambda, ale dostępne wewnątrz tej funkcji, są również dostępne w kodzie lambda. 252 Obiekt lambda: 1. Kiedy przekazujemy wyrażenie lambda do funkcji, tworzymy nowa klasę oraz obiekt tej klasy, którym będzie argument wywołania tej funkcji. 2. Odpowiednio, kiedy używamy auto, żeby zdefiniować zmienną zainicjalizowaną wyrażeniem lambda, zmienna ta jest obiektem nowo utworzonej klasy. 3. Klasa utworzona z wyrażenia lambda posiada listę pól odpowiadających przechwyconym zmiennym; są one inicjalizowane, kiedy obiekt jest tworzony. 253 Obiekt lambda: auto wc = find_if(words.begin(), words.end(), [sz](const string &a) { return a.size() >= sz; ); wygeneruje klasę, która będzie wyglądała tak, jak klasa SizeComp poniżej: class SizeComp { SizeComp(size_t n): sz(n) { // każdy kolejny parametr // konstruktora odpowiada jednej przechwytywanej zmiennej; // operator wywołania, który zwraca daną takiego samego // typu, ma takie same parametry i kod jak wyrażenie lambda bool operator()(const string &a) const { return a.size() >= sz; private: size_t sz; // pola odpowiadają przechwytywanym zmiennym 254 Przechwytywanie zmiennych: Zmienne lokalne przechwycone przez wyrażenie mogą zostać przechwycone w trybie przez referencję bądź przez wartość. W przykładach dotychczas przez wartość: void fcn1() { size_t v1 = 42; // zmienna lokalna auto f = [v1]{ return v1; // kopiowanie v1 do obiektu v1 = 0; auto j = f(); // j==42 ponieważ f przechowuje kopię v1 Zmienne przechwycone przez referencję: void fcn2() { size_t v1 = 42; // zmienna lokalna // obiekt f2 przechowuje referencję do v1 auto f2 = [&v1] { return v1; v1 = 0; auto j = f2(); // j==0 ponieważ f2 ma referencję do v1 Uwaga: kiedy przechwytujemy obiekt przez referencję, musimy być pewni, że będzie nadal istniał w momencie działania kodu wyrażenia lambda 255 256 13
Zmienne przechwycone przez referencję: Przykład, kiedy przechwycenie przez referencję jest konieczne: void funwords(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' ') { // tutaj kod, który np. porządkuje słowa // wg określonego kryterium // a teraz kod który je wypisuje do strumienia // podanego w argumencie wywołania funkcji 'funwords' for_each(words.begin(), words.end(), [&os, c](const string &s) { os << s << c; ); 257 Zmienne przechwycone przez referencję - podsumowanie: Zmienne typów bazowych int, double najprościej przechwytywać w obiekcie lambda w trybie przez wartość. Kiedy przechwytujemy w trybie przez referencję, np. iteratory, lub wskaźniki, należy zapewnić istnienie wskazywanych struktur danych, kiedy obiekt lambda będzie wykonywany, oraz zapewnić, że zawartość będzie taka, jakiej oczekiwaliśmy. Funkcja, w której powstał obiekt lambda, może go na zakończenie zwrócić, ale zmienne lokalne funkcji nie mogą być w nim przechowane przez referencję. 258 Zmienne przechwycone implicite: [=] przechwycenie przez wartość wszystkich zmiennych lokalnych z otoczenia [&] przechwycenie przez referencję wszystkich zmiennych lokalnych z otoczenia [=, lista referencji] przechwycenie przez referencję zmiennych wymienionych w liście referencji, natomiast przez wartość wszystkich pozostałych zmiennych lokalnych z otoczenia [&, lista referencji] przechwycenie przez wartość zmiennych wymienionych w liście referencji, natomiast przez referencję wszystkich pozostałych zmiennych lokalnych z otoczenia Zmienne przechwycone implicite: sprawdzenie, czy długość jest większa od wartości przechowywanej w zmiennej sz: wc = find_if(words.begin(), words.end(), [=](const string &s) { return s.size() >= sz; ); 259 260 Zmienne przechwycone implicite i explicite: Przykład na mieszane przechwytywanie implicite i explicite (domyślne i jawne): char c = ' '; for_each(words.begin(), words.end(), [=, &os](const string &s) { os << s << c; ); Dla mieszanego sposobu przechwytywania pierwszym elementem w liście przechwytywanych zmiennych musi być & lub =. Ten symbol ustala domyślny sposób przechwytywania, który będzie użyty wobec zmiennych niewymienionych po przecinku. Zmienne wymienione po przecinku musza być przechwytywane w sposób przeciwny do domyślnego. Modyfikacja przechwyconych zmiennych: Domyślnie w kodzie lambdy nie wolno jest modyfikować zmiennych przechwyconych przez wartość. Jeżeli potrzebna jest modyfikacja, stosujemy słowo kluczowe mutable size_t v1 = 42; // zmienna lokalna auto f3 = [v1] () mutable { return ++v1; v1 = 0; auto j = f3(); // j == 43 261 262 14
Modyfikacja przechwyconych zmiennych: O tym, czy zmienna przechwycona sposobem przez referencje może być zmieniana, decyduje wyłącznie to, czy referencja dotyczy zmiennej typu const, czy nie. size_t v1 = 42; // zmienna lokalna, która nie jest const // 'v1' jest referencją na zmienną lokalną nie-const auto f2 = [&v1] { return ++v1; // modyfikuje 'v1' v1 = 0; auto j = f2(); // j == 1 Specyfikowanie typu zwracanych danych: Kiedy w kodzie wyrażenia lambda występuje tylko instrukcja return nie ma potrzeby specyfikowania typu zwracanych danych, np. : transform(vi.begin(), vi.end(), vi.begin(), [](int i) { return i < 0? -i : i; ); Kiedy w kodzie wyrażenia występuje jakiekolwiek inne wyrażenie niż return, domyślnie wyrażenie zwraca void. transform(vi.begin(), vi.end(), vi.begin(), [](int i) { if (i<0) return -i; else return i; ); Pojawia się problem, bo miała być zwrócona wartość absolutna, a nie ma nic.. 263 264 Specyfikowanie typu zwracanych danych: Aby jawnie podać zwracany typ danych należy typ zwracanych danych zadeklarować w miejscu tuż za nagłówkiem z wykorzystaniem strzałki -> (tj. wg notacji trailing return type); transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int { if (i < 0) return -i; else return i; ); C++ 11: Tablice (kontenery) mieszające 265 Tablica mieszająca to uogólnienie zwykłej tablicy. Czynność wyszukiwania jest dużo szybsza niż w listach i kontenerach asocjacyjnych. W tablicy zwykłej element o kluczu k trafia na pozycję k (zbiór danych jest reprezentowany jako tablica, gdzie każdej pozycji odpowiada wartość klucza k z uniwersum kluczy. Wady: pola nie wykorzystane pozostają puste, k nie musi być indeksem ze zdefiniowaną arytmetyką) W tablicy mieszającej element o kluczu k jest przechowywany w pozycji h(k), gdzie h to funkcja kodująca (haszująca). Funkcja h odwzorowuje zbiór kluczy na zbiór pozycji w tablicy. Wady: też są.. (np. problem konfliktów), ale i tak jest szybciej. 267 Szybkość: Zamiast logarytmicznego czasu dostępu dla kontenerów przechowujących dane w postaci drzew binarnych stały czas dostępu, niezależnie od rozmiaru danych (brzmi słodko, do tego jest jednak jeszcze pewien tekst drobnym druczkiem, ale o tym jeszcze nie teraz) Zamiana wartości klucza na wartość indeksu tablicowego daje ten efekt. Przykład takiego postępowania: funkcje isalpha i isdigit, które sięgają do tablic indeksowanych 256 wartościami wszystkich możliwych znaków ASCII i sprawdzają wartość komórki reprezentującej flagę prawda/fałsz. To jest jednak niepraktyczne, kiedy dziedzina kluczy jest wielka, a zbiór wykorzystanych wartości niewspółmiernie mniejszy. 268 15
Tablica mieszająca zarządza zbiorem kubełków, które są indeksowane liczbami całkowitymi. Wartość kluczowa k jest haszowana funkcją h, która zamienia ją na liczbę całkowitą z szerokiego zakresu wartości, a następnie wartość przypisana do tego klucza jest wkłada do kubełka wskazanego przez tę liczbę (w praktyce indeks jest obliczany jako wynik działania: h(k) modulo całkowita liczba kubełków). Każdy kubełek może zawierać więcej niż jeden element, które są przechowywane w postaci listy. Aby odczytać wartość dla zadanego klucza, obliczane jest h(k) i określany kubełek, w którym następnie odbywa się tradycyjne poszukiwanie żądanego elementu. Kiedy liczba danych w kontenerze będzie rosła, operacja haszowania oraz dostęp do kubełka pozostanie stały, ale czas szukania w kubełku będzie liniowy, a jego wartość będzie zależała od liczby elementów w kubełku. Aby utrzymać stały czas dostępu 1. liczba kubełków musi rosnąć wraz ze wzrostem liczby danych. 2. funkcja mieszająca powinna równomiernie rozkładać elementy po kubełkach. To oznacza, że o ile logarytmiczny czas dostępu w standardowych kontenerach jest gwarantowany, o tyle stały czas w kontenerach mieszających nie. A zły wybór funkcji mieszającej oraz mała liczba kubełków da w konsekwencji liniowy czas dostępu. 269 270 Dwa symptomy, że coś jest źle: Nierówno wypełnione kubełki oraz przepełnione kubełki. Lekarstwo: przemieszanie (rehashing) kontenera zwiększenie liczby kubełków i ponowna dystrybucja elementów. Kontenery mieszające: 1. unordered_set 2. unordered_multiset 3. unordered_map 4. unordered_multimap Ale to i tak nie pomoże na słabą funkcję mieszającą. W tym przypadku najlepszym wyjściem są testy obciążeniowe na dużych zbiorach wiarygodnych danych wykonane jeszcze przed oddaniem aplikacji do użytku. 271 272 Choć kontener jest unordered, to metody begin() i end() działają intuicyjnie zwracają pierwszy i ostatni element. Za to nie ma żadnej relacji porządkującej elementy pobrane przez odwołanie się do wartości sąsiedniej iteratora są podawane w kolejności takiej, jak to aktualnie wynika z funkcji mieszającej i kubełków raczej nie do odgadnięcia dla użytkownika. W razie spadku szybkości dostępu przemieszanie odbywa się automatycznie, ale można je też wymusić. Mamy też dostęp do podstawowych parametrów: liczba kubełków, liczba elementów w każdym kubełku oraz jakie są aktualne elementy w danym kubełku, aby samemu podjąć decyzję co do zmiany funkcji mieszającej czy też przemieszania. 273 unordered_set<int> uset; int int_value = 2;... uset.insert(int_value); // w razie potrzeby - przemiesza... unordered_set<int>::iterator it = uset.find(int_value); if(it!= uset.end()) cout << "found!" << endl; else cout << "not found!" << endl;... for(unordered_set<int>::iterator it = uset.begin(); it!= uset.end(); ++it) cout << *it << endl; // wartości w porządku nieznanym 274 16
typedef unordered_map<string, int> Umap_t; Umap_t umap; string string_var("dobromir"); int int_var = 5; umap[string_var] = int_var; umap.insert(umap_t::value_type("szpak", 4)); Umap_t::iterator itm = umap.find(string_var); if(itm!= umap.end()) cout << "klucz: " << itm->first << " ma: " << itm->second << endl; else cout << "brak!" << endl; for(umap_t::iterator itm = umap.begin(); itm!= umap.end(); ++itm) cout << "[" << itm->first << "," << itm->second << "] " << endl; Dostarczanie własnej metody porównania i mieszającej Przechowując w zbiorze mieszającym (unordered_set) dane naszego własnego typu, należy dostarczyć dwóch mechanizmów niezbędnych do poprawnego działania kontenera: 1. porównanie np. jako metoda zdefiniowana w naszej klasie. 2. mieszanie (w celu obliczenia indeksu dla zadanej wartości naszego typu) np. jako obiekt funkcyjny. Ich nagłówek jest dokładnie określony, tylko wewnętrzne działanie pozostaje w gestii autora kodu. 275 276 Dostarczanie własnej metody porównania i mieszającej Metoda porównująca: class MojTyp { // typ danych przechowywany w zbiorze public: bool operator== (const MojTyp& t) const { /* tutaj porównanie */ Funkcja mieszająca: struct Hash_MojTyp { // mieszający obiekt funkcyjny dla MojTyp std::size_t operator() (const MojTyp& t) const { /* tutaj obliczenie na podstawie wartości MojTyp */ Dostarczanie własnej metody porównania i mieszającej Jeżeli nie ma możliwości edytować plik z definicją klasy reprezentującej typ przechowywanych danych, to należy przygotować obiekt funkcyjny porównujący: struct Equal_MojTyp { // porównujacy obiekt funkcyjny dla MojTyp bool operator() (const MojTyp& t1, const MojTyp& t2) const { /* tutaj porównanie, zwraca 'true' tylko jeżeli (!(t1<t2) &&!(t1>t2)) */ 277 278 Zbiór mieszający zdefiniowany do przechowywania naszego typu danych Te dwa mechanizmy należy zadeklarować jako kolejne dwa parametry szablonu reprezentującego kontener: unordered_set< MojTyp, Hash_MojTyp, Equal_MojTyp> zbior_mojtyp; Strojenie parametrów kontenera Aby ocenić efektywność kontenera, potrzebujemy znać kluczowe parametry pracy: cout << "Liczba elementów: " << umap.size() << endl; cout << "Liczba kubełków: " << umap.bucket_count() << endl; cout << "wsp. załadowania: " << umap.load_factor() << endl; cout << "max wsp. załadowania: " << umap.max_load_factor() << endl; Chyba, że porównanie jest metodą klasy MojTyp. Wtedy wystarczy tylko: unordered_set< MojTyp, Hash_MojTyp> zbior_mojtyp; Max wsp. załadowania poziom, który powoduje automatyczne uruchomienie procesu przemieszania. 279 280 17
Strojenie parametrów kontenera Sprawdzenie zajętości kubełków: for(int i = 0; i < umap.bucket_count(); ++i) { cout << "kubełek " << i << " zawiera " << umap.bucket_size(i) << " elementów " << endl; Przejrzenie zawartości kubełków: for(int i = 0; i < umap.bucket_count(); ++i) { cout << "kubełek " << i << " zawiera: "; for(umap_t::local_iterator it = umap.begin(i); it!= umap.end(i); ++it) { cout << "[" << it->first << ", " << it->second << "] "; 281 Strojenie parametrów kontenera Sprawdzenie działania funkcji mieszającej: Umap_t::hasher hfunc = umap.hash_function(); // wskaźnik do funkcji for(umap_t::const_iterator it = umap.begin(); it!= umap.end(); ++it) { cout << it->first << " jest mieszane na " << hfunc(it->first) << endl; cout << endl; 282 Porównanie efektywności Źródło: Using TR1ʼs unordered_set and unordered_map, David Kieras, EECS Department, University of Michigan, October 18, 2008, white paper. Dokonano eksperymentalnego porównania szybkości działania programu: 1. posługującego się dwoma typami kontenerów: klasyczny set i pochodzący z TR1 unordered_set 2. dla dwóch typów danych przechowywanych w tych kontenerach: tanich i kosztownych obliczeniowo w porównywaniu. Razem cztery przypadki. Porównanie efektywności Tanie obliczeniowo typy danych: class Cheap { private: int i; public: Cheap(int i_ = -1) : i(i_) { typedef int key_type; key_type get_key() const {return i; bool operator< (const Cheap& rhs) const {return i < rhs.i; bool operator== (const Cheap& rhs) const {return i == rhs.i; friend ostream& operator<< (ostream& os, const Cheap& x); void print() const {cout << *this << endl; 283 284 Porównanie efektywności Kosztowne obliczeniowo typy danych: class Expensive { private: string s; public: Expensive(int i = -1) { ostringstream oss; oss << "abcdefghijklmnopqrstuvwxyz" << 10000000 + i; s = oss.str(); //długie łańcuchy, różnią się na ostatnich polach typedef string key_type; const key_type& get_key() const {return s; bool operator< (const Expensive& rhs) const {return s < rhs.s; bool operator== (const Expensive& rhs) const {return s == rhs.s; friend ostream& operator<< (ostream& os, const Expensive& x); 285 Porównanie efektywności Funkcja mieszająca (szablon klasy bazowej funktora): template <typename T> struct MyHash { size_t operator()(const T&) const { assert(!"myhash unspecialized operator() has been called!"); return 0; 286 18
Porównanie efektywności Tania funkcja mieszająca (konkretyzacja szablonu) : template <> struct MyHash<Cheap> { size_t operator()(const Cheap& c) const { return static_cast<size_t>(c.get_key()); // just use the integer key value Porównanie efektywności Kosztowna funkcja mieszająca (konkretyzacja szablonu): template <> struct MyHash<Expensive> { size_t operator()(const Expensive& e) const { const string& s = e.get_key(); size_t result = 0; for (string::const_iterator i = s.begin(); i!= s.end(); ++i) result = (result * 131) + *i; return result; 287 288 Parametry eksperymentu 1. Warianty zajętości kontenera: 10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000 elementów. 2. Działanie: milion wywołań instrukcji szukającej dla każdego przypadku. 3. Pomiar: sumaryczny czas miliona wywołań (w sekundach) 4. Sprzęt: Mac Pro (quad Intel processor 2.66 GHz, 2 GB). 289 Czas dostępu do kontenera 290 STL: biblioteki alternatywne 1. SGI - Silicon Graphics International Corp., (początkowo Silicon Graphics Inc.) powstała na początku lat 80 (założyciele: Jim Clark, były profesor uniwersytetu Stanforda oraz grupa jego studentów): https://www.sgi.com/tech/stl/stl_introduction.html 2. Dinkumware założona w 1995 aby licencjonować biblioteki rozwijane przez P. J. Plaugera. Ostatnie osiągnięcia: pierwsza kompletna implementacja bibliotek wg założeń i wymagań standardu C++11 (dla Microsoft Visual Studio 11 Beta): http://docwiki.embarcadero.com/radstudio/xe7/en/dinkumware_standard_c%2b%2b_library Czas dostępu do kontenera skala logarytmiczna 291 292 19
STL: obliczenia matematyczne Wsparcie dla obliczeń matematycznych Jest wiele bibliotek: 1. Boost - http://www.boost.org, sekcja Math and numerics: http://www.boost.org/doc/libs/?view=category_math 2. Eigen http://eigen.tuxfamily.org 3. Armadillo http://arma.sourceforge.net Dziękuję 293 294 20