Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

pl:dydaktyka:jimp2:2017:labs:wyjatki [2017/04/25 04:21]
mwp [Zgłoszenie wyjątku]
pl:dydaktyka:jimp2:2017:labs:wyjatki [2019/06/27 15:50]
Linia 1: Linia 1:
-======Wyjątki====== 
-=====Czym są wyjątki===== 
-Wyjątki to inaczej błędy generowane podczas wykonywania programu - sytuacje wyjątkowe, anomalie. 
  
-W C%%++%% istnieje specjalny mechanizm do obsługi takich sytuacji. 
- 
-=====Podstawy obsługi wyjątków===== 
-Większość sytuacji wyjątkowych można rozwiązać z użyciem //​standardowych//​ mechanizmów: ​ 
-  * konstrukcje //​if..else//:​ <code cpp> if(!myfile){ 
-    cout << "Nie można otworzyć pliku!"​ << endl; 
-    return 1; 
-  }else{ 
-    //​wczytywanie 
-  }</​code>​ 
-  * konstrukcje z //assert//: <code cpp>#​include <​assert.h>​ 
-... 
-int a, b, c 
-cin >> a >> b; 
-// Program zostanie zatrzymany gdy b == 0 
-assert(b); 
-c=a/​b;</​code>​ 
- 
-Te konstrukcje wymagają jednak, aby sytuacja wyjątkowa była rozwiązana lokalnie: w miejscu w którym się pojawiła. Rozpatrywanie większości sytuacji wyjątkowych w miejscu ich wystąpienia nie jest wygodne ani wręcz możliwe (nie dysponujemy odpowiednim kontekstem w jaki sposób powinna zostać obsłużony błąd). ​ 
- 
-<code cpp> 
-int bar(int v) { 
-  if (v == 2) { 
-    ... 
-    return 0; //OK 
-  } else { 
-   //​sugeruje blad funkcja spodziewala sie  
-   //​dwóch parametrów programu 
-    return -1; 
-  } 
-} 
- 
-inf foo(int args_number) { 
-  int result = bar(args_number);​ 
-  if (result < 0) { 
-     //​jeśli nastąpił błąd w funkcji bar 
-     //​propaguje wynik do wywołania 
-     ​return result; 
-  } else { 
-    ... 
-  } 
-} 
- 
-int main(int argc, char *argv[]) { 
-  int result = foo(argc); 
-  if (result < 0) { 
-     //​dopiero z punktu wiedzenia funkcji main wiadomo, 
-     //że w tym wypadku należy poinformować użytkownika 
-     //o błędzie 
-     cerr << "nie poprawna ilość argumentów"​ << end; 
-     ​return -1; 
-  } 
-  ... 
-  return 0; 
-} 
-</​code>​ 
- 
-Alternatywna metoda w postaci zwracania kodów błędu z funkcji jest w praktyce niewygodna do stosowania i łatwo o przoczenie instrukcji warunkowej sprawdzającej przypadek błędnego wykonania. 
- 
-Mając daną metodę klasy //Matrix// odpowiedzialną za dodawanie macierzy i zwracającą obiekt klasy macierz będący wynikiem dodawania, w jaki sposób można przekazać informację o tym, że wymiary podanych macierzy są nieprawidłowe do kodu wywołującego tę metodę, gdzie już wiadomo jak należy obsłużyć tą wyjątkową sytuację a z pominięciem wszystkich pośrednich wywołań funkcji (takich jak foo powyżej)? Odpowiedzią na ten problem są wyjątki. 
- 
-Mechanizm wyjątków pozwala na przerwanie działania programu w momencie pojawienia się sytuacji wyjątkowej i //​wycofanie//​ się do fragmentu kodu który tę sytuację wie jak obsłusłużyć (np. poprosić użytkownika o ponowne wprowadzenie danych, albo zamknięcie programu, albo ciche pominięcie problemu): 
- 
-<code cpp> 
-#include <​stdexcept>​ 
-#include <​iostream>​ 
- 
-using namespace std; 
- 
-// Klasa reprezentująca naszą sytuację wyjątkową 
-class WrongDimensionsException : public invalid_argument { 
-  public: 
-    WrongDimensionsException(const Matrix &m) : invalid_argument{"​Wrong dimensions of matrix"​},​ m_{m}{} 
-  public: 
-    Matrix GetMatrix() const { 
-       ​return m_; 
-    } 
-  private: 
-    Matrix m_;  ​ 
-}; 
- 
-class Matrix{ 
-  public: 
-    ... 
-    Matrix add(const Matrix& m); 
-  private: 
-    int rows, cols; 
-    double** tab; 
-}; 
- 
-Matrix Matrix::​add(const Matrix& m) { 
-  // jeśli wymiary nie są prawidłowe,​ to zgłoś wyjątek 
-  if( !validDimensions(*this,​m) ) { 
-      throw WrongDimensionsException(m);​ 
-  } 
- 
-  Matrix result; 
-  for(int r = 0; r < rows; r++){ 
-    for(int c = 0; c < cols; c++){ 
-      result.set(r,​c,​this.tab[r][c]+m.tab[r][c]);​ 
-  ​ 
-  return result; 
-  ​ 
-} 
- 
-int static_main(){ 
-  Matrix m1("[1 2 ; 3 4]"); 
-  Matrix m2("[3 4 4; 5 6 4]"); 
-  ​ 
-  try { 
-    cout << m1.add(m2) << endl; 
-  } catch(const WrongDimensionsExcepion &w) { 
-    // jesli wyjątek zostanie zgłoszony program wejdze tutaj  
-    // i wyświetli informacje 
-    cerr << w.what() << ": " << w.GetMatrix() << endl; 
-  } 
-} 
- 
-int user_main() {  ​ 
-  while(true) { 
-    try { 
-      std::string m1_str; 
-      std::string m2_str; 
-      cin >> m1_str; 
-      cin >> m2_str; 
-      Matrix m1(m1_str); 
-      Matrix m2(m2_str); 
-      cout << "​Result is: " << m1.add(m2) << endl; 
-    } catch(const WrongDimensionsExcepion &w) { 
-      // nawet jesli wyjątek zostanie zgłoszony zostanie tutaj wyświetlona 
-      // informacja dla użytkownika 
-      cerr << "Wrong dimensions of Matrices. Please enter matrices once again..."​ << endl; 
-    } 
-  } 
-} 
- 
-int main() { 
-  static_main();​ 
-  user_main();​ 
-} 
-</​code>​ 
- 
-=====Zwijanie stosu===== 
-Kiedy wyjątek zostaje wyrzucony program cofa się aż do miejsca gdzie znajduje się blok //catch//, który go obsługuje. 
-Dla wszystkich obiektów które znajdowały się w obszarach z których program się wycofuje wywoływane są destruktory. 
- 
-Problem pojawia się jednak gdy bezpośrednio alokujemy pamięć. 
- 
-{{.:​exceptions.png |Zwijanie stosu}}<​code cpp> 
-int foo() { 
-  string s("ala ma kota"​);​ 
-  bar(); 
-  return 0; 
-} 
- 
-int bar() { 
-  // pamięć nie  
-  // zostanie zwolniona 
-  int * tab = new int[100]; 
-  fido(); 
-  return 0; 
-} 
- 
-int fido() { 
-  Matrix m("[1 2 3; 3 4 5]"); 
-  throw runtime_error(__func__ + " error in " + __FILE__ + " at " + __LINE__); 
-  return 0; 
-} 
- 
-int main(){ 
-  try{ 
- 
-    foo(); 
- 
-  } catch(const exception &e){ 
-    cerr << e.what() << endl; 
-  } 
- 
-  cout << "​Dalej"​ << endl; 
-} 
-</​code>​ 
- 
-=====Zgłoszenie wyjątku===== 
-Do zgłoszenia wyjątku (wyrzucenia wyjątku) służy słowo kluczowe **throw**. Operator //throw// posiada jeden parametr (w szczególnych przypadkach nie posiada żadnego parametru), który może być dowolnego typu. Oznacza to, ze jako wyjątek możemy wyrzucić dowolny obiekt dowolnej klasy. Zazwyczaj jednak pisze się osobne klasy dla wyjątków o nazwach tłumaczących sens danego wyjątku. 
- 
-Istnieje także specjalny plik nagłówkowy //​exception//​ zawierający domyślną implementacje klasy do zgłaszania wyjątków. 
- 
-<code cpp> 
-using namespace std; 
- 
-Matrix Matrix::​div(const Matrix& m) { 
-  // jeśli wymiary nie są prawidłowe,​ to zgłoś wyjątek 
- 
-  if( !validDimensions(*this,​m) ) throw WrongDimensionException(m);​ 
-  if( zeroDeterminant(m) ) throw DivisionByZero(m);​ 
- 
-  ... 
-  ​ 
-} 
-</​code>​ 
-=====Przechwycenie wyjątku===== 
-====try/​catch==== 
-Do przechwytywania wyjątków służy konstrukcja **try/​catch**. Blok //try// powinien obejmować wszystkie operacje potencjalnie generujące wyjątki. Po zamknięciu bloku try następuje jeden lub kilka bloków //catch// które zostaną uruchomione w przypadku zaistnienia wyjątku który obsługują. Istnieje także specjalna konstrukcja bloku //catch//, która działa analogicznie jak //else//: 
- 
-<code cpp> 
-#include <​iostream>​ 
-#include "​Matrix.h"​ 
-#include "​WrongDimensionException.h"​ 
-#include "​DivisionByZero.h"​ 
- 
- 
-int main(){ 
-  Matrix m1("[1 2 ; 3 4]"); 
-  Matrix m2("[3 4 4; 5 6 4]"); 
-  ​ 
-  try{ 
-    cout << m1.div(m2) << endl; 
-  }catch(WrongDimensionException w){ 
-    w.printMessage();​ 
-  }catch(DivisionByZero d){ 
-    d.printMessage();​ 
-  }catch(...){ 
-    cout << "​Został zgłoszony inny wyjątek!"​ << endl; 
-  } 
-} 
-      
-</​code>​ 
- 
-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. 
- 
-====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. ​ 
- 
-Aby ustawić swoją własną funkcje, która zostanie wywołana w przypadku wystąpienia niespodziewanego wyjątku należy przekazać do //​set_unexpected//​ wskaźnik do funkcji zwracającej //void// i nie pobierającej żadnych elementów. 
- 
-<code cpp> 
-// set_unexpected example 
-#include <​iostream>​ 
-#include <​exception>​ 
-using namespace std; 
- 
-void myunexpected () { 
-  cerr << "​unexpected called\n";​ 
-  throw 0;     // throws int (in exception-specification) 
-} 
- 
-void myfunction () throw (int) { 
-  throw '​x'; ​  // throws char (not in exception-specification) 
-} 
- 
-int main (void) { 
-  set_unexpected (myunexpected);​ 
-  try { 
-    myfunction();​ 
-  } 
-  catch (int) { cerr << "​caught int\n";​ } 
-  catch (...) { cerr << "​caught other exception \n"; } 
-  return 0; 
-} 
- 
-</​code>​ 
- 
-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===== 
-//​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: ​ 
- 
-<code cpp> 
-#include <​iostream>​ 
-#include <​exception>​ 
-#include "​WrongDimensionException.h"​ 
-#include "​DivisionByZero.h"​ 
- 
-using namespace std; 
- 
-class Matrix{ 
-  ... 
-  public: 
-    ... 
-    // Metoda może zgłosić ejden z dwóch wyjątków określonych 
-    // jako parametry operatora throw. Zakładamy, ze klasy 
-    // WrongDimensionsExcpetion i DivisionByZero są zaimplementowane 
-    Matrix div(const Matrix&​) throw (WrongDimensionsExcpetion,​DivisionByZero);​ 
-}; 
- 
-</​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===== 
- 
-====Wyjątki w konstruktorze==== 
-Dobrym przykładem wykorzystania wyjątków jest ich wyrzucanie w konstruktorach obiektów. Konstruktor nie posiada zwracanego typu, dlatego w przypadku niepowodzenia w utworzeniu obiektu, nie ma możliwości zwrócenia kodu błędu. W wyniku niepowodzenia konstruktora powstaje tak zwany //obiekt zombie// - istnieje, ale nie jest popranym obiektem. 
- 
-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. 
- 
-====Wyjątki w destruktorze==== 
- 
-Nigdy nie należy rzucać wyjątków w destruktorach! Jak zostało powiedziane w poprzedniej sekcji ([[#​wyjatki_w_konstruktorze|Wyjątki w konstruktorach]]) w przypadku kiedy zostaje zgłoszony wyjątek w konstruktorze,​ dla wszystkich obiektów składowych danego obiektu wywoływane są destruktory. Zakładając,​ że któryś z tych destruktorów wyrzuciłby wyjątek, //c++ runtime// jest w sytuacji bez wyjścia: który z wyjątków anulować, a który wyrzucić dalej? ​ 
- 
-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. 
- 
-=====Dziedziczenie i wyjątki===== 
-(:!: do zrozumienia tej sekcji wymagana jest znajomość mechanizmu dziedziczenia w C%%++%% -> laboratorium [[.:​dziedziczenie|Dziedziczenie i polimorfizm]]) 
- 
-Ponieważ jako wyjątek można zgłosić dowolny obiekt, może zdążyć się, że wyrzuconych zostanie kilka obiektów z tej samej hierarchii dziedziczenia. Dodatkowo jeśli metoda ma wyspecyfikowany jako rzucany wyjątek A, to może wyrzucić wyjątek B, który dziedziczy po A i nie zostanie wywołana funkcja //​unexpected//​. 
- 
-Mechanizm dziedziczenia w odniesieniu do wyjątków pozwala na polimorficzne przetwarzanie wyjątków, ale jednocześnie wymusza pamiętanie o tym, że kolejność bloków //catch// jest ważna podczas kombinacji dziedziczenie-wyjątki. 
- 
-Rozważ poniższy kod: 
-<code cpp> 
-#include <​iostream>​ 
-using namespace std; 
- 
-class CircleException{ 
-  // Oznacza ze nie mozna wyrysowac kola 
-}; 
- 
-class BallException : public CircleException{ 
-  // Oznacza ze nie mozna wyrysowac kuli 
-}; 
- 
-void drawBall() throw (BallException){ 
-  throw BallException();​ 
-} 
- 
-int main(){ 
-  try{ 
-    drawBall(); 
-  }catch(CircleException a){ 
-    cout << "Blad podczas rysowania kola" << endl; 
-  }catch(BallException b){ 
-    cout << "Blad podczas rysowania kuli" << endl; 
-  } 
- 
-}</​code>​ 
- 
-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//​. ​ 
- 
-======Ćwiczenia====== 
-  - 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? 
-  - 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 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}. 
-  - Jeśli masz dostępną swoją klasę Matrix (laboratorium [[.:​klasy2|Klasy i obiekty II]]), dopisz dla niej obsługę wyjątków. ​ 
pl/dydaktyka/jimp2/2017/labs/wyjatki.txt · ostatnio zmienione: 2019/06/27 15:50 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0