======Dynamiczne zarządzanie pamięcią 2====== ===== Wyrażenia regularne ===== Biblioteka wyrażeń regularnych została dołączona do standaradu C%%++%%11 i jest dostępna w pliku nagłówkowym: #include Utworzenie nowego wyrażenia regularnego jest jeszcze prostsze z surowymi łańcuchami znaków (w których nie interpretuje się ukośnika jako znaku specjalnego) regex pattern {R"((\w+)\s+(\d{1,3}))"}; Powyższe wyrażenie regularne dopasuje się do wyrazu składającego się z co najmniej jednej litery **\w**, po którym następuje odstęp złożony z co najmniej jednego białego znaku **\s** i wreszcie ciągu składającego się z od 1 do 3 cyfr **\d**. Dodatkowo zostały zastosowane grupy przechwytujące (kolejne grupy to kolejne nawiasy okrągłe), dzięki którym można się odnieść do fragmentów dopasowania. regex pattern {R"((\w+)\s+(\d{1,3}))"}; string line {"Adam 789"}; smatch matches; if (regex_match(line, matches, pattern)) { cout<<"udało się dopasować do linii: "< Oprócz **regex_match**, dostępne są też funkcje **regex_search** i **regex_replace**. Zapoznać się z dokumentacją [[http://en.cppreference.com/w/cpp/regex|wyrażenia regularne]] ===== Statyczne tablice ++ ===== W C%%++%%11 została wprowadzony dodatkowy kontener implementujący statyczną tablicę w pliku nagłówkowym #include Deklaracja nowej tablicy: //inicjalizacja tablicy intów o rozmiarze 3 za pomocą zadanych wartości: array tab {1,2,3}; //alternatywnie jednorodna inicjalizacja tablicy boolów o romiarze 10: array flags; flags.fill(false); Po tablicy można iterować bez problemów: for (auto v : tab) { cout << "value: " << v << endl } ===== Zbiory ===== Zbiór, czyli kolekcja niepowtarzalnych elementów. Dostępna jest po dołączeniu: #include Przykład użycia zbioru: set s {4,5,6,7}; if (s.find(5) != s.end()) { cout<<"5 jest elementem zbioru"< W przypadku użycia zbioru, sprawdzenie, czy element znajduje się w zbiorze ma złożoność logarytmiczną w przeciwieństwie do liniowej złożoności wyszukiwania w tablicy. [[http://en.cppreference.com/w/cpp/container/set|Dokumentacja do zbioru]]. =====Słowo kluczowe using===== Za pomocą słowa kluczowego using w C%%++%%11 można wprowadzić kilka różnych definicji. - selektywny import symboli do globalnej przestrzeni nazw using ::std::unique_ptr; - definicja nowego aliasu dla typu (odpowiednik **typedef**) #include using Url = std::string; bool IsValid(const Url &url); - import wszystkich symboli z danej przestrzeni nazw (niezalecane) using namespace std; ===== Inteligentne wskaźniki ===== Zapoznać się z tekstem [[http://umich.edu/~eecs381/handouts/C++11_smart_ptrs.pdf|Smart Pointers]]. Inteligentne wskaźniki automatycznie zarządzają dostępem do powierzonej im pamięci i gdy pamięć nie jest już używana zostaje automatycznie zwolniona. Opisane poniżej funkcjonalności weszły w standardzie C++14. ==== unique_ptr ==== Najprostszy z inteligentnych wskaźników. Jedyny właściciel pamięci, gdy zmienna traci zakres pamięć jest automatycznie zwolniona. Definicja znajduje się w pliku nagłówkowym #include Definiowanie wskaźnika i przydzielenie mu nowego obiektu Type: unique_ptr p = make_unique(); Można też zapisać to krócej z wykorzystaniem auto: auto p = make_unique(); Wskaźnika można używać jak zwykłego wskaźnika: unique_ptr p = make_unique(); cout<value; Unikalny wskaźnik jest jedynym właścicielem pamięci, dlatego jeśli chcemy przypisać obiekt unique_ptr do innego musimy jawnie przenieść element: unique_ptr origin = make_unique(3); //Błąd kompilacji: unique_ptr new_owner = origin; //Przeniesienie odpowiedzialności: unique_ptr real_new_owner = move(origin); if (oring == nullptr) { cout<<"W tym momencie utworzony obiekt o wartości 3 już nie należy do origin"< Wtedy funkcja, która jako parametr przyjmuje unique_ptr rząda ona tak na prawdę przekazania zarządzania nad obiektem: unique_ptr GiveMeItForASec(unique_ptr p) { ++(*p); return p; } void foo() { auto ptr = make_unique(99); //BLAD KOMPILACJI: auto new_ptr = GiveMeItForASec(ptr); //OK auto new_ptr = GiveMeItForASec(move(ptr)); //TERAZ ptr nie zawiera nic } Wciąż jednak można pozostawić właściciela w spokoju i przekazać unique_ptr przez stałą referencję lub wskaźnik (druga opcja brzmi dziwnie, ale ujednolica konwencję, patrz niżej). ==== shared_ptr ==== Innym typem wskaźnika jest współdzielony wskaźnik, którego można współdzielić z innymi obiektami tego typu i pamięć zostanie zwolniona w momencie, gdy ostatni wskaźnik przestanie istnieć. Jest to możliwe, dzięki zliczaniu ilości odwołań do wskazanego fragmentu pamięci, które się odbywa automatycznie. shared_ptr p = make_shared(78); //poniższa linia jest poprawna shared_ptr another = p; //W tym momencie oba wskaźniki pokazują na ten sam obszar pamięci, a wewnętrzny licznik odwołań wynosi 2 Minusem wykorzystania współdzielonych wskaźników inteligentnych jest większe zużycie pamięci (gdzieś musi być przechowany licznik odwołań), dlatego z tych wskaźników zaleca się korzystać w ostateczności jeśli zależy nam na minimalizowaniu zużycia pamięci. Z drugiej strony jeśli nie zależy nam na pamięci, a z jakiegoś dziwnego powodu wciąż chcemy pisać aplikację w C%%++%% 8-), wykorzystanie shared_ptr w większości przypadków powinno rozwiązać problemy z wyciekami pamięci. ==== weak_ptr ==== Ten wskaźnik jest dodany w celu rozwiązania problemu cyklicznych odwołań współdzielonych wskaźników wzajemnie na siebie (wtedy licznik odwołań nigdy nie spadnie do zera i pamięć nigdy nie zostaje zwolniona). Dokładniejszy opis w tekście podanym w literaturze. ===== Przestrzenie nazw ===== Przestrzenie nazw porządkują definicje symboli i pozwalają wykluczyć konflikty pojęć z różnych kontekstów przypadkowo tak samo nazywające się. Symbole to nazwy metod, nazwy zmiennych globalnych, nazwy struktur i klas, nazwy typów, itp... Definicja nowej przestrzeni nazw może wyglądać następująco: #ifndef MODULE_H #define MODULE_H #include namespace mymodule { std::string ToString(int value); } #endif // MODULE_H #include #include #include "Module.h" namespace mymodule { using ::std::string; using ::std::stringstream; string ToString(int value) { stringstream ss; ss< #include "Module.h" int main() { auto str = mymodule::ToString(100); } ===== Typ pary i krotki ===== Typ pary i krotki stanowi w C%%++%%11 odpowiednik anonimowej struktury, którą można użyć w dowolnym miejscu kodu, bez potrzeby jej definiowania. Np. std::pair p {"abc",17}; std::tuple t3 {"Mam",17.3,"lat"}; W celu poprawy czytelności kodu, można wykorzystać **using** np. using Carry = bool; using Code = char; std::pair NextCode(Code c); ===== Przekazywanie argumentów do funkcji ===== Argumenty do funkcji można przekazywać w C%%++%% na wiele różnych sposobów, natomiast należy brać pod uwagę czytelność zarówno kodu klienta (wywołujący metodę), jak i kod implementujący metodę. Stąd na laboratoriach będziemy korzystać z następującej konwencji: - argumenty o typie **Type** do funkcji przekazujemy przez **const Type &** w celu uniknięcia potencjalnego kopiowania pamięci (referencja), ale zabezpieczamy się przed modyfikacją argumentu w metodzie (cost) std::string ToString(const Type &t); - wyjątek stanowią argumenty typów prostych takich jak int, double, bool, itd.. które przekazujemy przez wartość std::string ToString(bool b); - jeśli chcemy zwrócić z funkcji dwa lub więcej elementów można wykorzystać typy **std::tuple** i **std::pair** std::pair NextChar(char c); - jeśli zachodzi potrzeba zmodyfikowania argumentu wewnątrz funkcji przekazujemy argument przez wskaźnik(klasyczny) void Modify(Type *t); Konwencja jest znacznie czytelniejsza od zastosowania niestałej referencji z punktu widzenia klienta:Type t; Modify(&t); Kod przekazuje jawnie adres co sygnalizuje możliwość zmodyfikowania obiektu przez funkcję. ====== Ćwiczenia ====== - **[2 punkty] [[https://leetcode.com/problems/encode-and-decode-tinyurl/#/description|LeetCode]] Przygotwać bibliotekę wspomagającą tworzenie skróconych adresów URL. W tym celu może pomóc Zdefniowanie metody generotora, która jest testowana w pierwszym kroku testów. Na podstawie aktualnego stanu generatora (tablica 6 znaków) wyznacza następny stan. Tablicę stanu można dalej przechowywać w strukturze TinyUrlCodec** * Moduł: **tinyurl** * Pliki z implementacją: **TinyUrl.h/cpp** * Używana struktura danych: **TinyUrlCodec** * Sygnatury metod: std::unique_ptr Init(); void NextHash(std::arrray *state); std::string Encode(const std::string &url, std::unique_ptr *codec); std::string Decode(const std::unique_ptr &codec, const std::string &hash); * Przestrzeń nazw: **tinyurl** * Importy: #include #include #include #include - [2 plusy] [[https://leetcode.com/problems/minimum-time-difference/#/description|LeetCode]] Przygotować metodę, która wyliczy różnicę czasu pomiędzy czasami w formacie HH:MM lub H:MM w minutach. Z pośród wielu godzin należy znaleźć najmniejszą różnicę między dwoma godzinami. * Moduł: **minimaltimedifference** * Pliki z implementacją: **MinimalTimeDifference.h/cpp** * Sygnatury metod: unsigned int ToMinutes(std::string time_HH_MM); unsigned int MinimalTimeDifference(std::vector times); * Przestrzeń nazw: **minimaltimedifference** * Importy: #include #include #include #include - [2 plusy] Przygotować bibliotekę udostępniającą stukturę **Counter** umożliwiającą zliczanie obiektów i metody ją wspierające. Motoda Init ma za zadanie stworznie obiektu i ewentualne jego zaincjalizowanie, metoda Inc ma za zadanie zwiększenie licznika obiektu o 1, metoda Counts ma zwrócić aktulany licznik obiektów, jeśli nigdy nie był inkremetowany licznik dla tego klucza, powinno zostać zwrócone 0 i wreszcie metoda SetCountsTo ma za zadanie ustawienie licznika na zadaną wartość. * Moduł: **ccounter** * Pliki z implementacją: **CCounter.h/cpp** * Używana struktura danych: **Counter** * Sygnatury metod: std::unique_ptr Init(); void Inc(std::string key, std::unique_ptr* counter); int Counts(const std::unique_ptr &counter, std::string key); void SetCountsTo(std::string key, int value, std::unique_ptr *counter); * Przestrzeń nazw: **ccounter** * Importy: #include #include #include - **[3 punkty] Napisz bibliotekę wspierającą budowę drzew binarnych z wykorzystaniem wskaźników inteligentnych. ** * Moduł: **smarttree** * Pliki z implementacją: **SmartTree.h/cpp** * Używana struktura danych: **SmartTree** * Sygnatury metod: std::unique_ptr CreateLeaf(int value); std::unique_ptr InsertLeftChild(std::unique_ptr tree, std::unique_ptr left_subtree); std::unique_ptr InsertRightChild(std::unique_ptr tree, std::unique_ptr right_subtree); void PrintTreeInOrder(const std::unique_ptr &unique_ptr, std::ostream *out); std::string DumpTree(const std::unique_ptr &tree); std::unique_ptr RestoreTree(const std::string &tree); * Przestrzeń nazw: **datastructures** * Importy: #include #include #include