15. Zpracování výjimek I
  1. C++ Builder podporuje zpracování výjimek C++, zpracování strukturovaných výjimek C a zpracování výjimek VCL. Příklady uvedené v této kapitole pro C++ a C výjimky mohou být přeloženy jako konzolové aplikace.

  2. Výjimky popisují výjimečné situace, které vyžadují speciální zpracování a mohou popisovat chyby vznikající za běhu aplikace (např. dělení nulou nebo nedostatek volného místa v paměti). Zpracování výjimek nabízí způsob řešení chyb. Obsluhu výjimky můžeme použít pro specifikaci akce, která bude provedena po vzniku chyby před ukončením programu. Jsou zpracovávány pouze synchronní výjimky, tzn. výjimky, které jsou generovány naší aplikací. Jazyk C++ určuje, že všechny výjimky vznikají v bloku try (chráněný blok). Tento blok je následován jedním nebo více bloky catch, které identifikují a zpracují výjimky generované v bloku try.
    Zpracování výjimek vyžaduje použití tří klíčových slov: try, catch a throw. Když program C++ způsobí výjimku, pak řízení je předáno na jinou část programu (obsluhu výjimky), která zpracovává tento typ výjimky. Obsluha výjimku zachycuje.
    Program výjimku generuje provedením příkazu throw. Příkaz throw se obecně vyskytuje uvnitř funkce a má tvar:
    throw "overflow";
    Tento příkaz vytvoří objekt, který popisuje typ výjimky (v našem případě aritmetické přetečení). Jiná část programu může zachytit vygenerovaný objekt výjimky a zpracovat jej. K použití zpracování výjimek, musíme obklopit náš kód konstrukcí try / catch. Syntaxe této konstrukce je:
    blok-try:
      try složený-příkaz seznam-obsluh
    seznam-obsluh:
      obsluha [seznam-obsluh]
    obsluha:
      catch (deklarace-výjimky) složený-příkaz
    deklarace-výjimky:
      seznam-specifikací-typů deklarátor
      seznam-specifikací-typů abstraktní-deklarátor
      seznam-specifikací-typů
    příkaz-throw:
      throw [přiřazovaní-výraz]
    Klíčová slova try, catch a throw nejsou v jazyku C povolena. Blok try musí být následován blokem catch. Příkazy v bloku try jsou prováděny běžným způsobem. Pokud v bloku try je generována výjimka, pak řízení provádění programu je předáno na příslušnou obsluhu výjimky. Obsluha je blok kódu určený k zpracování výjimky. V jazyku C++ musí být za blokem try alespoň jedna obsluha. Program musí obsahovat obsluhu pro každou výjimku, která může být programem generována.
    Přestože výjimky mohou být libovolného typu, je vhodné vytvářet objekty výjimek. Objekty výjimek jsou brány jako libovolné jiné objekty. Výjimka nese informace z místa vzniku výjimky do místa jejího zachycení. Jedná se o informaci, kterou uživatel aplikace potřebuje znát při výskytu chyby za běhu aplikace. C++ má řadu předdefinovaných výjimek.
    Blok kódu, ve kterém výjimka může vzniknout, musí předcházet klíčové slovo try. Jestliže výjimka nastane, pak běh programu je přerušen a program hledá příslušnou obsluhu výjimky. Pokud obsluha je nalezena, pak řízení je na ní předáno, není-li nalezena, pak voláním set_terminate mohla být nastavena funkce, která bude provedena; jinak program volá funkci terminate. Pokud žádná výjimka nenastane, pak program je proveden normálně.
    Následuje řada příkladů, ve kterých jsou použity různé způsoby generování výjimek. Následující příklad předává objekt Out obsluze (je potřeba jej přeložit jako konzolovou aplikaci):
    #include <conio.h>
    #include <iostream.h>
    bool pass;
    class Out{};
    void festival(bool firsttime){ if(firsttime) throw Out(); }
    int main()
    {
      try
      { pass = true;
        festival(true);
      }
      catch(Out& e){ pass = false; }
      cout << (pass ? "true" : "false") << endl;
      getch();
      return 0;
    }
    Zde v bloku try je volána funkce festival, která generuje objekt Out. Tento objekt je zachycen obsluhou (je zde změněna hodnota proměnné pass na false). Následující příklad generuje zachycenou výjimku znova (příkaz throw bez parametru). Výjimka již musí existovat.
    #include <conio.h>
    #include <iostream.h>
    bool pass;
    class Out{};
    void festival(bool firsttime){ if(firsttime) throw Out(); }
    void test()
    {
      try { festival(true); }
      catch(Out& e){ pass = false; throw; }
    }
    int main()
    {
      try
      { test();
      }
      catch(Out& e){ pass = true; }
      cout << (pass ? "true" : "false") << endl;
      getch();
      return 0;
    }
    Funkce test volá funkci festival, ve které je generována výjimka Out. Výjimka je zachycena obsluhou v test a opětovně generována (předána ven z funkce). Další příklad volá námi nastavenou ukončující funkci (my_terminate) při pokusu o opětovné generování výjimky, která nenastala.
    #include <conio.h>
    #include <iostream.h>
    bool pass;
    class Out{};
    void my_terminate(){
      cout << "Žádná výjimka" << endl;
      getch();
      exit(1);
    }
    void festival(bool firsttime){ if(firsttime) throw Out(); }
    void test()
    {
      try { festival(false); }
      catch(Out& e){ pass = false; }
      throw;  // nelze opětovně generovat výjimku, která nenastala
    }
    int main()
    {
      set_terminate(my_terminate);
      try {
        test();
      }
      catch(Out& e){ pass = true; }
      cout << (pass ? "true" : "false") << endl;
      getch();
      return 0;
    }
  3. Další příklad specifikuje seznam výjimek, které mohou být generovány jednotlivými funkcemi (festival a test). Ve funkci festival může vznikat výjimka Out a ve funkci test žádná výjimka.

  4. #include <conio.h>
    #include <iostream.h>
    bool pass;
    class Out{};
    void festival(bool firsttime) throw(Out) // pouze výjimka Out
    {
      if(firsttime) throw Out();
    }
    void test() throw() // žádná výjimka
    {
      try {
        festival(true);
      }
      catch(Out& e){ pass = true; }
    }
    int main()
    {
      pass = false;
      test();
      cout<<(pass?"test zpracoval výjimku":"výjimka nezpracována")<<endl;
      getch();
      return 0;
    }
    Pokud festival generuje jinou výjimku než Out, pak je považována za neočekávanou výjimku a řízení programu je předáno na funkci unexpected, jak je ukázáno v následujícím příkladě.
    Následující příklad ukazuje test, která nemá generovat žádnou výjimku. Pokud nějaká funkce (např. operátor new) v těle test generuje výjimku, pak výjimka musí být zpracována uvnitř test. Jinak výjimka porušuje seznam specifikací výjimek pro test. Funkcí set_unexpected můžeme nastavit jinou funkci pro neočekávanou výjimku; jinak, je volána funkce unexpected.
    #include <conio.h>
    #include <iostream.h>
    bool pass;
    class Out{};
    void my_unexpected(){
      cout << "Chyba test" << endl;
      getch();
      exit(1);
    }
    void festival(bool firsttime) throw(Out) // pouze výjimka Out
    {
      if(firsttime) throw Out();
    }
    void test() throw() // žádná výjimka
    {
      try { festival(true); }
      catch(Out& e){ pass = true; throw; } // opětovné generování Out - chyba
    }
    int main()
    {
      set_unexpected(my_unexpected);
      pass = false;
      test();
      cout<<(pass?"test zpracoval výjimku":"výjimka nezpracována")<<endl;
      getch();
      return 0;
    }
    Když výjimka nastane, pak příkaz-throw inicializuje dočasný objekt typu T (typ parametru par) použitý v throw(T par). Další kopie mohou být generovány podle požadavků překladače. Je tedy užitečné definovat pro objekt výjimky kopírovací konstruktor, jak je ukázáno v následujícím příkladě.
    #include <conio.h>
    #include <iostream.h>
    class festival
    {
    public:
      festival() { cout << "vytvoření festival" << endl;  }
      festival(const festival&){cout<<"vytvoření kopie festival" << endl;}
      ~festival() { cout << "zrušení festival" << endl; }
    };
    int main()
    {
      try {
        cout << "generování výjimky festival" << endl;
        throw(festival());
      }
      catch(festival&){
        cout << "vytvoření festival" << endl;
      }
      getch();
      return 0;
    }
  5. Obsluha výjimky je specifikována klíčovým slovem catch, které je umístěno bezprostředně za blok try. Může se také vyskytnout bezprostředně za jiným blokem catch. Každá výjimka generovaná programem musí být zachycena a zpracována obsluhou výjimky. Obsluha zachycuje výjimku, když se její typ shoduje (nebo může být převedena) s typem v příkazu catch. Při shodě je řízení předáno obsluze. Obsluha určuje, které akce mají být provedeny. Po dokončení obsluhy, program pokračuje za poslední obsluhou pro současný blok try. Žádná další obsluha pro současnou výjimku není vyhodnocena. K přenosu řízení mimo obsluhu může být použit příkaz goto. Pokud vznikne chyba při provádění obsluhy výjimky, pak program je ukončen. Obsluhu výjimek si ukážeme na následujícím příkladě.

  6. #include <conio.h>
    #include <iostream.h>
    class festival{};
    class Harvest : public festival{};
    class Spring : public festival{};
    void ToHaveFun(int i)
    {
      if(i==1) throw(Harvest());
      else     throw(Spring());
    }
    int main()
    {
      try {
        ToHaveFun(0);
      }
      catch(const Harvest&){cout << "generováno Harvest Festival" << endl; }
      catch(const Spring&) {cout << "generováno Spring Festival" << endl; }
      try {
        ToHaveFun(1);
      }
      catch(const Harvest&){cout << "generováno Harvest Festival" << endl; }
      catch(const Spring&) {cout << "generováno Spring Festival" << endl; }
      getch();
      return 0;
    }
    Další příklad ukazuje, že když zachytíme výjimku a tato výjimka je součástí hierarchie tříd, pak je nutno začít zachytáváním nejvíce odvozené třídy výjimky.
    #include <conio.h>
    #include <iostream.h>
    class festival{};
    class harvest  : public festival{};
    class spring : public festival{};
    void ToHaveFun(int i)
    {
      if (i==1) throw(harvest());
      else if(i==2) throw(spring());
           else throw(festival());
    }
    int main()
    {
      /* Při zachycování výjimek záleží na pořadí */
      try { ToHaveFun(0); }
      catch(const harvest&){cout << "generováno Harvest Festival" << endl; }
      catch(const spring&) {cout << "generováno Spring Festival" << endl; }
      catch(const festival& ){cout << "generováno Festival" << endl; }
      try { ToHaveFun(1); }
      catch(const harvest&){cout << "generováno Harvest Festival" << endl; }
      catch(const spring&) {cout << "generováno Spring Festival" << endl; }
      catch(const festival& ){cout << "generováno Festival" << endl; }
      try { ToHaveFun(2); }
      catch(const harvest&){cout << "generováno Harvest Festival" << endl; }
      catch(const spring&) {cout << "generováno Spring Festival" << endl; }
      catch(const festival& ){cout << "generováno Festival" << endl; }
      /* Zachytávání základní třídy dříve než odvozených tříd způsobí, že
         odvozená výjimka je zachycena obsluhou základní třídy výjimky */
      try { ToHaveFun(1); }
      catch(const festival&){cout<<"generováno Festival (je správné?)"<<endl;}
      catch(const harvest&){cout << "generováno Harvest Festival" << endl; }
      catch(const spring&) {cout << "generováno Spring Festival" << endl; }
      try { ToHaveFun(2); }
      catch(const festival&){cout <<"generováno Festival (je správné?)"<<endl;}
      catch(const harvest&){cout << "generováno Harvest Festival" << endl; }
      catch(const spring&) {cout << "generováno Spring Festival" << endl; }
      getch();
      return 0;
    }
    V následujícím příkladu příkaz catch (...) zachytí výjimku libovolného typu. Tento příkaz je jediná obsluha výjimky bloku try.
    #include <conio.h>
    #include <iostream.h>
    bool pass;
    class Out{};
    void festival(bool firsttime) throw(Out)
    {
      if(firsttime) throw Out();
    }
    void test() throw()
    {
      try { festival(true); }
      catch(...){ pass = true; }
    }
    int main()
    {
      pass = false;
      test();
      cout<<(pass?"test zpracoval výjimku":"výjimka nezpracována")<<endl;
      getch();
      return 0;
    }
  7. C++ poskytuje službu nazvanou specifikace výjimek, která umožní specifikovat seznam výjimek, které funkce může generovat. Tato specifikace se zapisuje jako přípona deklarace funkce. Použití přípony specifikací výjimek neovlivňuje typ funkce. V předchozích příkladech specifikace výjimek již byla použita. Následují ukázky deklarací funkcí se specifikacemi výjimek:

  8. void f1();                // Funkce generuje libovolné výjimky
    void f2() throw();        // Funkce negeneruje žádnou výjimku
    void f3() throw( A, B* ); // Funkce může generovat výjimky odvozené od A
                              // nebo od ukazatele na B
    Pokud funkce generuje výjimku neuvedenou ve specifikaci, pak program volá unexpected.
  9. Win32 podporuje zpracování strukturovaných výjimek C, které se podobají výjimkám C++. Jsou zde jisté odchylky, což vyžaduje opatrnost při míchání těchto dvou typů výjimek. Při používání strukturovaných výjimek je nutno v aplikacích C++ Builderu dodržovat tato pravidla:
  10. V C programech používáme k implementaci strukturovaných výjimek klíčová slova __except, __finally a __try. Klíčové slovo __try lze použít pouze v C programech. Pokud chceme zapsat přenositelný kód, pak strukturované výjimky není vhodné používat.
    Strukturované výjimky mohou být zpracovány pomocí rozšíření zpracování C++ výjimek:
    try {
      funkce();
    }
    __except(__expr__) {
      // obsluha
    }
    __expr__ je výraz, jehož vyhodnocení je jedna ze třech hodnot: EXCEPTION_CONTINUE_SEARCH (0 - obsluha není zahájena a OS pokračuje v hledání obsluhy výjimky), EXCEPTION_CONTINUE_EXECUTION (-1 - provádění pokračuje v bodě výjimky) a EXCEPTION_EXECUTE_HANDLER(1 - obsluha výjimky je provedena).
    Win32 poskytuje dvě funkce, které mohou být použity k zjištění informací o aktivní výjimce: GetExceptionCode a GetExceptionInformation. Jestliže chceme tyto funkce volat jako část výrazu filtru, pak je musíme volat přímo v __except. Výraz filtru slouží k filtrování výjimek.
  11. Pokud mícháme C++ a strukturované výjimky, pak nesmíme zapomenout na některé věci. I když C++ Builder implementuje C++ výjimky se strukturovanými výjimkami Win32, pak v bloku __except jsou výjimky C++ transparentní. Blok try může být následován jedním blokem __except nebo alespoň jedním blokem catch. Tyto bloky nelze míchat, ale je možno vnořit dva bloky try do sebe:

  12. try {
      EXCEPTION_POINTERS *xp;
      try {
        funkce();
      }
      __except(xfilter(xp = GetExceptionInformation())) {
        //...
      }
    }
    catch (...) {
      //...
    }
    Míchání výjimek si ukážeme na následujícím příkladě. Přeložte jej jako konzolovou aplikaci a pokuste se pochopit, kdy výjimky vznikají a jak jsou zpracovány.
    #include <iostream.h>
    #include <conio.h>
    class Exception
    {
    public:
      Exception(char* s = "Unknown"){ what = strdup(s);      }
      Exception(const Exception& e ){ what = strdup(e.what); }
      ~Exception()                  { delete[] what;         }
      char* msg() const             { return what;           }
    private:
      char* what;
    };
    int main()
    {
      float e, f, g;
      try {
        try {
          f = 1.0;
          g = 0.0;
          try {
            cout << "výjimka: " << endl;
            e = f / g;
          }
          __except(EXCEPTION_EXECUTE_HANDLER) {
            cout << "Zachycení strukturované výjimky. " << endl;
            throw(Exception("Chyba: Dělení 0"));
          }
        }
        catch(const Exception& e) {
          cout << "Zachycení C++ výjimky: " << e.msg() << endl;
        }
      }
      __finally {
        cout << "C++ umožňuje také použít __finaly " << endl;
      }
      getch();
      return e;
    }
  13. Jak již jsme viděli v předchozím příkladě, model zpracování strukturovaných výjimek podporuje ukončující blok (__finally). Tento blok je prováděn vždy (když výjimka vznikne i když nevznikne).

  14. try {
      funkce();
    }
    __finally {
      // zde uvedené příkazy jsou provedeny vždy
    }
    Použití ukončovacího bloku je uvedeno v následujícím příkladě.
    #include <conio.h>
    #include <iostream.h>
    int main()
    {
      float e, f, g;
      try {
        f = 1.0;
        g = 0.0;
        try {
          cout << "Výjimka: " << endl;
          e = f / g;
        }
        __except(EXCEPTION_EXECUTE_HANDLER) {
          cout << "Zachycení výjimky. " << endl;
        }
      }
      __finally {
        cout << "Je provedeno také. " << endl;
      }
      try {
        f = 1.0;
        g = 2.0;
        try {
          cout << "Není výjimka: " << endl;
          e = f / g;
        }
        __except(EXCEPTION_EXECUTE_HANDLER) {
          cout << "Zachycení výjimky. " << endl;
        }
      }
      __finally {
        cout << "Je provedeno také. " << endl;
      }
      getch();
      return e;
    }
15. Zpracování výjimek I