C++-manual
Förutgående | Kapitel 20. Exceptions | Nästa

# Använda exceptions

Det finns två skilda moment involverade då man använder sig av exceptions, en part som försöker exekvera en viss del av programmet och fånga upp fel, och en ennan part som kan ge en exception. Då en exception aktiveras så kastar (throw) programmet en exception. Mottagaren har en skild sektion som är markerad som kritisk och exceptions som kastas från denna kritiska sektion fångas upp av en fångst-sektion. Vi ska se på ett konkret exempel på ett program som dividerar två tal som användaren matat in och som kastar en exception om man försöker dividera med 0:

    #include <iostream>
    #include <string>

    float divide (float X, float Y) {
      // är täljaren 0?
      if ( Y == 0 ) {
        // jep, kasta en exception
        throw string ("Nämnaren måste vara != 0");
      }

      // värdena ok, räkna och returnera
      return X / Y;
    }

    int main () {
      float X, Y;

      // evig slinga
      while ( 1 ) {
        cout << "Ge täljaren: ";
        cin >> X;
        cout << "Ge nämnaren: ";
        cin >> Y;

        // försök skriva ut kvoten och fånga fel
        try {
          // kritisk sektion
          cout << X << "/" << Y << " = " << divide (X, Y) << endl;
        }
        catch (string Error) {
          // ett fel har fångats, skriv ut meddelandet
          cout << Error << endl;
        }
      }
    }

Funktionen `divide()` kollar om nämnaren är 0, och i så fall kastas en exception. Detta görs med nyckelordet `throw`. Man kan kasta vilken datatyp som helst, allt från `int`, `string` till skräddarsydda klasser. I vårt fall villvi endast förmedla ett meddelande om att vi dividerar med 0. anroparen vet även att detta är det enda fel som kan uppstå. Ifall flera olika fel kunde uppstå i våra beräkningar kunde vi kasta t.ex. en `enum` och sedan i anroparen kolla vilket fel som inträffade. I `main()` finns en slinga som läser in två tal och försöker dividera dessa. Den sektion där programmet förväntar sig en exception är i ett s.k. `try`-block. Efter detta kommer att s.k. `catch`-block där man deklarerar de exceptions som man är förberedd på att ta emot. I programmet ovan tas en `string` emot. Man kan ha flera `catch`-block för att ta emot olika typer av exceptions. Allmänt ser en exception-deklaration ut på följande sätt:

    try {
      satser;
    }
    catch (datatyp1 variabel1) {
      satser;
    }
    catch (datatyp2 variabel2) {
      satser;
    }
    ...
    catch (...) {
      satser;
    }

De olika `catch`-blocken måste ha olika datatyp som de försöker fånga. Man kan placera en sista "fånga alla" `catch` med datatypen `...` (tre punkter) för att fånga upp exceptions som inte tidigare fångats. Ifall där man har klasser som exceptions kommer det kastade undantaget att provas uppifrån och ned bland `catch`-satserna tills någon passar eller man når en `...`-sats. Det är även möjligt att nästa exceptions, d.v.s. ha ett nytt `try-catch`-block innanför t.ex. en catch-sektion.

## Allokering och exceptions

Ett exempel på en operator som använder sig av exceptions är `new`, som kastar `bad_alloc` ifall den inte kunde allokera önskad mängd minne. Följande program allokerar minne tills det tar slut (ett bra exempel på faran med att läcka minne!):

    #include <iostream>
    #include <new>

    int main () {
      int Allokerat = 0;

      while ( 1 ) {
        // försök allokera minne evigt
        try {
          new char [ 1000000 ];

          // åter en MB alokerat
          Allokerat++;
        }
        catch ( bad_alloc ) {
          // minnet är slut
          cout << "Minnet slut efter " << Allokerat << "Mb" << endl;
          return 1;
        }
      }
    }

Allokeringen med `new` allokerar 1Mb med minne per gång och läcker det. Förr eller senare tar minnet slut i datorn och `new` misslyckas. Den kastar då `bad_alloc`. För att kunna använda denna exception måste man inkluderar `<new>`, som innehåller definitionen på `bad_alloc`.

## Exceptions och konstruktorer

Man kan även använda exceptions tillsammans med konstruktorer. Tittar man närmare på en konstruktor märker man att den inte kan returnera något till anroparen. En konstruktor har inget sätt med vilket den kan förmedla att den inte kunde exekveras korrekt. Det finns flera olika suboptimala lösningar på problemet men exceptions torde vara den bästa lösningen. Följande situation kunde uppstå för en klass `File` som abstraherar en fil då man försöker skapa en instans genom att ge et filnamn med:

    try {
      // försök öppna filen
      File * InputData = new File ( Filename );

      // filen öppnades ok, läs data
      ...
    }
    catch ( FileNotFound E ) {
      // ingen sådan fil hittades
    }
    catch ( PermissionDenied E ) {
      // inga rättigheter att öppna/läsa filen
    }
    catch ( ... ) {
      // annat fel, kanske minnesallokering
    }

    // programmet fortsätter med filen läst eller felet hanterat

## Återkasta en exception

Ett `catch`-block kan om det vill kasta vidare samma exception som det har fångat genom en tom `throw;` utan exception efter. På så vis skickas samma exception uppåt i anropshierarkin. Det gör det lätt för olika nivåer att reagera på ett felmeddelande utan större besvär. På så vis kan en `catch` på lägsta nivå försöka ordna upp felet innan den ger upp och skickar felet vidare till en högre nivå. Exemplet nedan visar hur man kan återkasta en exception.

    #include <iostream>

    // definiera en egen datatyp för exceptions
    enum Exception { Problem, Illa, Hemsk };

    void thrower () {
      // något illa har hänt!
      throw Illa;
    }

    void catcher () {
      // försök anropa thrower och se vad som händer
      try {
        thrower ();
      }
      catch ( Exception E ) {
        // är det något vi kan hanter
        if ( E == Problem ) {
          // allt är ok, vi kan hantera problemt
          cout << "Ingen fara!" << endl;
        }
        else {
          // allvarligt problem
          throw;
        }
      }
    }

    void dummy () {
      // ingen 'try' här inte
      catcher ();
    }

    int main () {
      // anropa 'dummy'
      try {
        dummy ();
      }
      catch ( Exception E ) {
        // vilken typ av exception är det frågan om?
        switch ( E ) {
        case Problem : cout << "Ett litet problem" << endl; break;
        case Illa :    cout << "Ett större problem" << endl; break;
        case Hemsk :   cout << "Ett hemskt problem" << endl; break;
        }
      }
    }

Vi har funktionen `thrower()` som kastar en exception av typen `Exception`. Det första `catch`-blocket i `catcher()` tar emot undantaget och kontrollerar vilken typ av exception det är. Om det är en typ som den kan hantera (`Problem`) hanteras problemet internt, annars är det ett större problem och en tom `throw` återkastar undantaget vidare. I funktionen `dummy()` finns inget `try-catch`-block så exceptionen skickas vidare till `main()`. Man behöver alltså inte ha ett `try-catch`-block på varje nivå, utan man kan kasta en exception mellan många nivåer. I `main()` kollas sedan vilken typ av excaption det var frågan om och denna hanteras på något sätt.

Notera den egna datatypen `Exception` som definierats för våra felmeddelanden. Den innehåller ingen text el.dyl., men i detta exempel är det endast typen av exception som är viktig.

Förutgående | Hem | Nästa
Exceptions | Upp | Ofångade exceptions