Both sides previous revision
Poprzednia wersja
Nowa wersja
|
Poprzednia wersja
|
pl:dydaktyka:jimp2:2017:labs:wyjatki [2017/04/25 04:15] mwp [Zwijanie stosu] |
pl:dydaktyka:jimp2:2017:labs:wyjatki [2018/04/30 17:35] mwp [Ćwiczenia] |
using namespace std; | using namespace std; |
| |
Matrix Matrix::div(const Matrix& m) throw (WrongDimensionException, DivisionByZero){ | Matrix Matrix::div(const Matrix& m) { |
// jeśli wymiary nie są prawidłowe, to zgłoś wyjątek | // jeśli wymiary nie są prawidłowe, to zgłoś wyjątek |
| |
} | } |
</code> | </code> |
| |
| |
=====Przechwycenie wyjątku===== | =====Przechwycenie wyjątku===== |
====try/catch==== | ====try/catch==== |
Oznacza on mniej więcej tyle: //jeśli pojawił się wyjątek, którego nie mają w parametrach wcześniejsze bloki catch ja się nim zajmę.// **Uwaga** Kolejność bloków catch ma znaczenie! Konstrukcja //catch(...)// pasuje do wszystkich wyjątków, dlatego powinna być umieszczana zawsze jako ostatnia. | Oznacza on mniej więcej tyle: //jeśli pojawił się wyjątek, którego nie mają w parametrach wcześniejsze bloki catch ja się nim zajmę.// **Uwaga** Kolejność bloków catch ma znaczenie! Konstrukcja //catch(...)// pasuje do wszystkich wyjątków, dlatego powinna być umieszczana zawsze jako ostatnia. |
| |
| |
| Jeśli dwa różne wyjątki mają wspólną klasę bazową można je przechwycić w pojedynczym bloku catch: |
| <code cpp> |
| int main() { |
| Matrix m1 ... |
| Matrix m2 ... |
| try { |
| m1.div(m2); |
| } catch (const invalid_argument &e) { |
| cerr << e.what() << endl; |
| } catch (...) { |
| //ten blok jest wstanie przechwycić każdy inny wyjątek |
| cerr << "Something went wrong" << endl; |
| } |
| } |
| </code> |
====Wyjątki nieoczekiwane==== | ====Wyjątki nieoczekiwane==== |
W przypadku kiedy wewnątrz funkcji pojawi się wyjątek, który nie znajduje się w specyfikacji wyjątków danej funkcji, wywoływana jest automatycznie funkcja //unexpected//, która z kolei wywołuje funkcje ustawioną przez //set_unexpected//. Domyślnie funkcją tą jest //terminate// - czyli zakończenie programu. | W przypadku kiedy wewnątrz funkcji pojawi się wyjątek, który nie znajduje się w specyfikacji wyjątków danej funkcji, wywoływana jest automatycznie funkcja //unexpected//, która z kolei wywołuje funkcje ustawioną przez //set_unexpected//. Domyślnie funkcją tą jest //terminate// - czyli zakończenie programu. |
Jeśli zdefiniowana przez programistę funkcja nie rzuca wyjątku określonego w specyfikacji funkcji (patrz [[#specyfikacja_wyjatkow|Specyfikacji wyjątków]], to program po jej wywołaniu i tak zostanie zakończony. | Jeśli zdefiniowana przez programistę funkcja nie rzuca wyjątku określonego w specyfikacji funkcji (patrz [[#specyfikacja_wyjatkow|Specyfikacji wyjątków]], to program po jej wywołaniu i tak zostanie zakończony. |
| |
=====Specyfikacja wyjątków===== | =====noexcept===== |
//Specyfikacja wyjątków// pozwala na zdefiniowanie listy wyjątków, które mogą być zgłoszone przez funkcję. Robi się to przy pomocy operatora //throw// umieszczonego po liście parametrów funkcji: | Specyfikacja noexcept pozwala na jawne zadeklarowanie metody jako nie wyrzucającej wyjątków, wtedy żadna pochodna metoda również nie może wyrzucać wyjątków, wszystkie pozozstałe motedy są traktowane jako potencjlanie wyrzucające wyjątki: |
| |
<code cpp> | <code cpp> |
public: | public: |
... | ... |
// Metoda może zgłosić ejden z dwóch wyjątków określonych | // Metoda potencjalnie wyrzuca wyjątki |
// jako parametry operatora throw. Zakładamy, ze klasy | Matrix div(const Matrix&); |
// WrongDimensionsExcpetion i DivisionByZero są zaimplementowane | // Metoda nie może wyrzucić wyjątku |
Matrix div(const Matrix&) throw (WrongDimensionsExcpetion,DivisionByZero); | virtual ~Matrix() noexcept; |
}; | }; |
| |
</code> | </code> |
| |
Umieszczenie pustej specyfikacji wyjątków (throw()) oznacza, ze funkcja nie powinna zgłaszać żadnych wyjątków. Jeśli jednak wewnątrz funkcji zostanie zgłoszony wyjątek, zostanie wywołana funkcja //unexpected//. Jeśli nie określi się żadnych wyjątków, to znaczy, że metoda może zgłaszać dowolne wyjątki. | |
| |
<code cpp> | |
int myfoo (int param) throw(); // nie można zgłaszać żądnych wyjątków | |
int mybar (int param); // wszystkie wyjątki dozwolone | |
</code> | |
| |
=====Konstruktory, destruktory i wyjątki===== | =====Konstruktory, destruktory i wyjątki===== |
| |
Wyjątki w konstruktorach wyrzuca się w analogiczny sposób jak w //zwykłych// metodach. Kiedy zostanie wyrzucony wyjątek w ciele konstruktora, mechanizm zwijający stos uruchomi destruktory wszystkich obiektów składowych danego obiektu. | Wyjątki w konstruktorach wyrzuca się w analogiczny sposób jak w //zwykłych// metodach. Kiedy zostanie wyrzucony wyjątek w ciele konstruktora, mechanizm zwijający stos uruchomi destruktory wszystkich obiektów składowych danego obiektu. |
| |
Problem pojawia się jednak kiedy w ciele konstruktora dynamicznie alokujemy pamięć. Nie zostanie ona w takim wypadku zwolniona. | W praktyce zawsze należy sprwdzić dziedzinę parametrów przekazanych w konstruktorze i pozowalać na utworzenie jedynie w pełni poprawnego obiektu, namtomiast w przeciwnym wypadku należy wyrzucić wyjątek. |
| |
| Problem pojawia się jednak kiedy w ciele konstruktora dynamicznie alokujemy pamięć. Nie zostanie ona w takim wypadku zwolniona. Chyba, że użyjemy smart pointery. |
| |
====Wyjątki w destruktorze==== | ====Wyjątki w destruktorze==== |
C++ gwarantuje co prawda, że w przypadku zaistnienia takiej sytuacji zostanie wywołana funkcja //terminate()// i program zostanie zamknięty, nie jest to jednak satysfakcjonujące rozwiązanie. | C++ gwarantuje co prawda, że w przypadku zaistnienia takiej sytuacji zostanie wywołana funkcja //terminate()// i program zostanie zamknięty, nie jest to jednak satysfakcjonujące rozwiązanie. |
| |
| Od wersji C++11 destuktory domyślnie są zadeklarowane jako noexcept. |
=====Dziedziczenie i wyjątki===== | =====Dziedziczenie i wyjątki===== |
(:!: do zrozumienia tej sekcji wymagana jest znajomość mechanizmu dziedziczenia w C%%++%% -> laboratorium [[.:dziedziczenie|Dziedziczenie i polimorfizm]]) | (:!: do zrozumienia tej sekcji wymagana jest znajomość mechanizmu dziedziczenia w C%%++%% -> laboratorium [[.:dziedziczenie|Dziedziczenie i polimorfizm]]) |
}; | }; |
| |
void drawBall() throw (BallException){ | void drawBall() { |
throw BallException(); | throw BallException(); |
} | } |
| |
Już podczas kompilacji otrzymujemy ostrzeżenie, że wyjątek //BallException// nigdy nie zostanie wychwycony. Dzieje się tak dlatego, że po napotkaniu pierwszego bloku //catch//, nastąpi automatyczne rzutowanie w górę i dopasowanie //BallException// do //CircleException//. | Już podczas kompilacji otrzymujemy ostrzeżenie, że wyjątek //BallException// nigdy nie zostanie wychwycony. Dzieje się tak dlatego, że po napotkaniu pierwszego bloku //catch//, nastąpi automatyczne rzutowanie w górę i dopasowanie //BallException// do //CircleException//. |
| |
| ===== Warto się zapoznać ===== |
| - [[http://www.exceptionsafecode.com/]] (filmiki z konferencji) |
| - [[http://en.cppreference.com/w/cpp/language/noexcept_spec]] |
| - [[http://www.gotw.ca/publications/mill22.htm]] |
| |
======Ćwiczenia====== | ======Ćwiczenia====== |
- Przetestuj przykład z sekcji [[#dziedziczenie_i_wyjatki|Dziedziczenie i wyjątki]]. Co zrobić, żeby wyjątek //BallException// był łapany poprawnie? | - [1 plus] Przetestuj przykład z sekcji [[#dziedziczenie_i_wyjatki|Dziedziczenie i wyjątki]]. Co zrobić, żeby wyjątek //BallException// był łapany poprawnie? |
- Napisz trzy metody, które będą posiadały wyspecyfkowane po jednym wyjątku jaki mogą wyrzucać. W każdej z metod wyrzuć wyjątek, ale inny od tego jaki został wyspecyfikowany. Za pomocą //set_unexpected// ustaw metodę, która będzie uruchamiana w takim wypadku. **Uwaga**: Program nie może się zakończyć w przypadku wywołania żadnej z 3 funkcji! Jak to osiągnąć pisząc tylko jedną funkcję //unexpected// i pamiętając, że program nie zostanie zatrzymany tylko wtedy kiedy funkcja //unexpected// wyrzuci wyjątek określony w specyfikacji funkcji, która wyrzuciła niepoprawny wyjątek? | - [3 plusy] Napisz klasę //PESEL//, która w konstruktorze przyjmuje ciąg znaków będących numerem PESEL. Klasa powinna posiadać metodę //validatePESEL(const char*)//, która sprawdza czy PESEL jest poprawny według algorytmu podanego na [[http://pl.wikipedia.org/wiki/PESEL|Wikipedii]]. Jeśli przekazany do konstruktora PESEL nie jest poprawny, program powinien wyrzucać wyjątek. Napisz funkcję //main// i przetestuj program. |
- Napisz klasę //PESEL//, która w konstruktorze przyjmuje ciąg znaków będących numerem PESEL. Klasa powinna posiadać metodę //validatePESEL(const char*)//, która sprawdza czy PESEL jest poprawny według algorytmu podanego na [[http://pl.wikipedia.org/wiki/PESEL|Wikipedii]]. Jeśli przekazany do konstruktora PESEL nie jest poprawny, program powinien wyrzucać wyjątek. Napisz funkcję //main// i przetestuj program. | - [3 plusy] Dla klasy Student dopisać metody walidujące argumenty przekazywane do klasy. W taki sposób by uniemożliwić storzenie niepoprawnego stanu obiektu. |
- Napisz program, który będzie służył do opóźniania, lub przyspieszania wyświetlania napisów do filmów w formacie MicroDVD ;-). Czas pojawienia się napisu oznaczany jest w tym formacie dwiema liczbami umieszczonymi pomiędzy nawiasami klamrowymi. Pierwsza liczba to numer klatki pojawienia się napisu, druga to numer klatki zniknięcia. Program powinien posiadać metodę //delay(const char* in, const char* out,int delay, int fps)// wykonującą opóźnienie (lub przyspieszenie) napisów o podaną ilość milisekund w zależności od tego jaki film ma //framerate//. \\ Program powinien przyjmować jako parametry cztery wartości: ścieżkę do pliku wejściowego, ścieżkę do pliku wyjściowego i liczbę milisekund i //framerate//. Metoda //delay// powinna wyrzucać wyjątek gdy w pliku pojawi się niepoprana sekwencja znaków. Np. zamiast {1234}{4565} pojawia się {sdfg}{33ff}. | * Student musi mieć co najmniej imię i nazwisko, każdy człon imienia musi być napisany z wielkiej litery pozostałe litery małe. Imię nie może zawierać cyfr i znaków specjalnych (amperad, dolar, procent, itp...) |
- Jeśli masz dostępną swoją klasę Matrix (laboratorium [[.:klasy2|Klasy i obiekty II]]), dopisz dla niej obsługę wyjątków. | * Wiek studenta musi mieścić się w zakresie 10 do 100 lat |
| * kierunek studiów musi zawierać się w zbiorze oferowanym akutalnie przez uczelnie XYZ: informatyka, ekonomia, matematyka, fizyka, filozofia |
| * w każdym wypadku, kiedy zostanie naruszony któryś z warunków należy zwrócić wyjątek dziedziczący po klasie invalid_argument, odpowiednio: |
| * InvalidNameSurname, InvalidNameCharacters, InvalidAge, InvalidProgram |
| * dopisać main z możliwością wczytywania danych o studencie i tworzącym studentów w pętli wstawiającym poprawnie utworzone obiekty do repozytorium. |
| |
| ======Zadanie domowe====== |
| Napisz program, który będzie służył do opóźniania, lub przyspieszania wyświetlania napisów do filmów w dwóch różnych formatach: MicroDVD i SubRip. Elementem centralnym interfejsu biblioteki jest abstrakcyjna klasa: **MovieSubtitles** z dwoma wirtualnymi metodami (przestrzeń nazw **moviesubs**): |
| * <code cpp>void ShiftAllSubtitlesBy(int offset_in_micro_seconds, int frame_per_second, std::istream *in, std::ostream *out)</code> - metoda nie powinna posiadać domyślnej implementacji (abstrakcyjna, czysto wirtualna). |
| * Destruktor (domyślna implementacja) |
| - **[1 punkt]** (lab8_micro_dvd_correct_cases_tests) Zaimplementuj klasę **MicroDvdSubtitles** implementującą metody z klasy bazowej **MovieSubtitles** i wspierającą format **MicroDvd**. Każda linia ma następujący format: //{INT_ON}{INT_OFF}SUBTITLE// gdzie nawiasy klamrowe wystepują w tej linii jak widać, natomiast INT_ON oznacza numer klatki filmu, w której pojawia się napis; INT_OFF numer klatki filmu w której znika napis; SUBTITLE oznacza napis do wyświetlenia. W tym formacie napis do wyświetlenia może zawierać dodatkowe informacje sterujące wyświetlaniem, np. | rozdzielający linie napisu, czy {y:b} wprowadzający pogrubienie. |
| - **[1 punkt]** (lab8_micro_dvd_error_cases_tests) Przesuwanie napisów powinno również obsługiwać przypadki, gdy podany plik (zawrtość strumienia wejściowego) zawiera niepoprawnie sformatowany tekst: |
| * próba przesunięcia klatek wstecz która skutkowałaby ujemnymi wartościami INT_ON i/lub INT_OFF powinna być zasygnalizowana przez wyjątkiem **NegativeFrameAfterShift** |
| * jeśli klatka ma zniknąć przed pojawieniem się (INT_ON >= INT_OFF) należy zasygnalizować to wyjątkiem **SubtitleEndBeforeStart** |
| * jeżeli jest inny błąd formatowania lini np. brak {INT_OFF}, brak nawiasu klamrowego, itp. należy zwrócić wyjątek **InvalidSubtitleLineFormat** |
| * jeżeli kolejne napisy pojawiają sie przed niż wcześniejszymi napisami powinine zostać zgłoszony wyjątek **OutOfOrderFrames** |
| * wszystkie te wyjątki powinny dziedziczyć po klasie **SubtitlesException**, która przyjmuje dwa argumenty w konstruktorze: numer linii, w której wystąpił błąd, treść tej linii (cała linia, nie tylko etykieta). A z kolei klasa **SubtitlesException** powinna dziedziczyć po klasie **std::invalid_argument** z biblioteki stdexcept |
| - **[1 punkt]** (lab8_sub_rip_correct_cases_tests) Zaimplementuj klasę **SubRipSubtitles** implementującą metody z klasy bazowej **MovieSubtitles** i wspierającą format **SubRip**. Każda napis ma następujący format: //INDEX NEW_LINE TIME_IN -> TIME_OFF NEW_LINE SUBTITLE1 NEW_LINE SUBTITLE2 NEW_LINE NEW_LINE//, gdzie INDEX - to numer klatki, NEW_LINE to znak nowej linii, TIME_IN i TIME_OFF to moment pojawienia się i zniknięcia klatki w formacie HH:MM:SS,mmm (godzina:minuta:sekundy,milisekundy). Napisy mogą być wielolinijkowe, nowa klatka pojawia się po pustej linii. |
| - **[1 punkt]** (lab8_sub_rip_error_cases_tests) poza wyjątkami wymienionymi w punkcie drugim należy zaimplementować też: |
| * numery klatek są nie pokolei **OutOfOrderFrames** |
| * jeśli klatka nie posiada secyfikacji kiedy miałaby się pojawić **MissingTimeSpecification** |
| - **[1 punkt]** (lab8_movie_subtitles_tests) sprawdzić czy wszystko działa ok. |