Föreläsningsanteckningar OOP-föreläsning 12, tisdag 16 oktober 2007 =================================================================== Idag ==== 1. Dålig felhantering 2. Bättre felhantering med undantag ("exceptions") 3. Undantag i C++ 4. Middags- och kaffeexemplet 5. Vektorklass med felkontroll 6. Kort: Felhantering i standardbibliotekets vektorklass, std::vector 1. Dålig felhantering ===================== Felhanteringen i C, och i ganska stor utsträckning C++, bygger på att programmeraren skriver kod som kontrollerar returvärden. Vad kan gå fel i följande C-program? unsafe-hello.c -------------- #include int main(int argc, char* argv[]) { printf("Hello, world!\n"); return 0; } /* main */ En säkrare version: safer-hello.c ------------- #include #include int main(int argc, char* argv[]) { if (printf("Hello, world!\n") != 14) { fprintf(stderr, "Kunde inte skriva hela texten!\n"); return EXIT_FAILURE; } if (fclose(stdout) != 0) { fprintf(stderr, "Kunde inte skriva till filen!\n"); return EXIT_FAILURE; } return 0; } /* main */ 2. Bättre felhantering med undantag ("exceptions") ================================================== Moderna språk (C++, Java, C# m. fl.) kan "kasta ett undantag" ("throw an exception") när något blir fel. Programmet kan sen "fånga" det undantaget, kanske på ett helt annat ställe än där det kastades. Liknelse: När man använder sig av ett subsystem (till exempel att man anropar metoden tvätta i objektet t av klassen Tvättmaskin) ska man inte behöva stå och kontrollera att den verkligen tvättar. I stället låter man den tvätta bäst den vill, och om något går fel kommer det ett undantagsobjekt flygande och träffar en i huvudet. Fördelar: * I en del språk MÅSTE programmeraren skriva kod för att fånga och hantera alla undantag (dvs fel) som kan uppstå. * (För vissa typer av undantag. Andra kan lämnas "ofångade".) * Ofångade undatag avslutar programmet med ett felmeddelande. * Dvs: Inga tysta fel, som man inte märker förrän nästa år när man tittar på filen som skulle ha skrivits! 3. Undantag i C++ ================= undantag.cpp ------------ #include using namespace std; int main () { try { throw 17; } catch (int e) { cout << "Det uppstod ett undantag: Undantag nummer " << e << endl; } } Utmatning --------- Det uppstod ett undantag: Undantag nummer 17 4. Middags- och kaffeexemplet ============================= Livet blir lättare med undantag: I många program består huvuddelen av programkoden av felhantering. Med felhantering med hjälp av undantag kan man både (a) samla ihop felhanteringskoden, i stället för att ha den utspridd i hela programmet, och (b) minska mängden felhanteringskod. En bra sak med undantag är att de kan kastas "genom" en metod. Även om den metoden inte gör någon felhantering alls, kan det kastade undantaget skickas vidare till anroparen. Studera detta C++-program som inte innehåller någon felhantering: fel-1.cpp --------- #include using namespace std; class kaffekanna { }; // kaffekanna* kp = new kaffekanna(); kaffekanna* kp = NULL; void at_forratt() { // Ät förrätten } void at_huvudratt() { // Ät huvudrätten } void at_efterratt() { // Ät efterrätten } void drick_kaffe() { // Drick kaffe } void at_middag() { at_forratt(); at_huvudratt(); at_efterratt(); drick_kaffe(); } int main() { at_middag(); cout << "Middagen uppäten.\n"; cout << "(Kanske i alla fall.)\n"; } Utmatning --------- Middagen uppäten. (Kanske i alla fall.) Vad händer om det uppstår ett fel någonstans, till exempel i funktionen drick_kaffe? Vi måste lägga till felhantering. Vi kan till exempel låta metoderna returnera true om allt gick bra, och false om något gick fel. Eller 0 respektive ett annat tal, som är konventionen i C: fel-2.cpp --------- #include using namespace std; class kaffekanna { }; // kaffekanna* kp = new kaffekanna(); kaffekanna* kp = NULL; int at_forratt() { // Ät förrätten. Om det gick bra: return 0; } int at_huvudratt() { // Ät huvudrätten. Om det gick bra: return 0; } int at_efterratt() { // Ät efterrätten. Om det gick bra: return 0; } int drick_kaffe() { if (kp == NULL) return -1; else // Drick kaffe. Det gick bra: return 0; } int at_middag() { if (at_forratt() == -1) return -1; else if (at_huvudratt() == -1) return -1; else if (at_efterratt() == -1) return -1; else if (drick_kaffe() == -1) return -1; else return 0; } int main() { if (at_middag() == 0) cout << "Middagen uppäten.\n"; else cout << "Middagen misslyckades.\n"; } Utmatning om det fanns en kaffekanna ------------------------------------ Middagen uppäten. Utmatning om det inte fanns en kaffekanna ----------------------------------------- Middagen misslyckades. Kommentar --------- Notera hur mycket jobb det blev i funktionen at_middag, och hur det programmet egentligen ska göra nästan försvinner bland all felhanteringen. Bättre med undantag! fel-3.cpp --------- #include using namespace std; class kaffekanna { }; // kaffekanna* kp = new kaffekanna(); kaffekanna* kp = NULL; class kaffeundantag : public exception { virtual const char* what() const throw(); }; const char* kaffeundantag::what() const throw() { return "Det fanns ingen kaffepanna.\n"; } void at_forratt() { // Ät förrätten } void at_huvudratt() { // Ät huvudrätten } void at_efterratt() { // Ät efterrätten } void drick_kaffe() { if (kp == NULL) throw kaffeundantag(); else { // Drick kaffe. Det gick bra. } } void at_middag() { at_forratt(); at_huvudratt(); at_efterratt(); drick_kaffe(); } int main() { at_middag(); cout << "Middagen uppäten.\n"; } Utmatning om det fanns en kaffekanna ------------------------------------ Middagen uppäten. Utmatning om det inte fanns en kaffekanna ----------------------------------------- terminate called after throwing an instance of 'kaffeundantag' what(): Det fanns ingen kaffepanna. Abort (core dumped) fel-4.cpp --------- #include using namespace std; class kaffekanna { }; kaffekanna* kp = new kaffekanna(); class kaffeundantag : public exception { virtual const char* what() const throw(); }; const char* kaffeundantag::what() const throw() { return "Det fanns ingen kaffepanna.\n"; } void at_forratt() { // Ät förrätten } void at_huvudratt() { // Ät huvudrätten } void at_efterratt() { // Ät efterrätten } void drick_kaffe() { if (kp == NULL) throw kaffeundantag(); else { // Drick kaffe. Det gick bra. } } void at_middag() { at_forratt(); at_huvudratt(); at_efterratt(); drick_kaffe(); } int main() { try { at_middag(); cout << "Middagen uppäten.\n"; } catch (kaffeundantag& e) { cout << "Middagen misslyckades.\n"; } } Utmatning om det fanns en kaffekanna ------------------------------------ Middagen uppäten. Utmatning om det inte fanns en kaffekanna ----------------------------------------- Middagen misslyckades. 5. Vektorklass med felkontroll ============================== Utdrag ur vektor-3.cpp ---------------------- // ... template class Vektor { private: T* lagring; int storlek; public: Vektor(int storlek); Vektor(const Vektor& original); ~Vektor(); const Vektor& operator=(const Vektor& original); T& Vektor::operator[](int i); }; // ... template T& Vektor::operator[](int i) { return lagring[i]; } Utdrag ur vektor-4.cpp ---------------------- // ... class FelaktigtIndexUndantag { }; template class Vektor { private: T* lagring; int storlek; public: Vektor(int storlek); Vektor(const Vektor& original); ~Vektor(); const Vektor& operator=(const Vektor& original); T& Vektor::operator[](int i); }; // ... template T& Vektor::operator[](int i) { if (lagring < 0 || i >= storlek) throw FelaktigtIndexUndantag(); return lagring[i]; } // ... int main() { Vektor v(3); v[0] = 7.1; v[2] = 5.9; cout << "v[0] == " << v[0] << endl; cout << "v[1] == " << v[1] << endl; v[7] = 1.17; } Utmatning --------- v[0] == 7.1 v[1] == 0 terminate called after throwing an instance of 'FelaktigtIndexUndantag' Abort (core dumped) Utdrag ur vektor-5.cpp ---------------------- int main() { try { Vektor v(3); v[0] = 7.1; v[2] = 5.9; cout << "v[0] == " << v[0] << endl; cout << "v[1] == " << v[1] << endl; v[7] = 1.17; } catch (FelaktigtIndexUndantag e) { cout << "Det uppstod ett fel: Felaktigt index\n"; } } Utmatning --------- v[0] == 7.1 v[1] == 0 Det uppstod ett fel: Felaktigt index vektor-6.cpp ------------ #include #include #include using namespace std; class FelaktigtIndexUndantag : public exception { private: int i, min, max; public: FelaktigtIndexUndantag(int i, int min, int max); virtual const char* what() const throw(); }; FelaktigtIndexUndantag::FelaktigtIndexUndantag(int i, int min, int max) { this->i = i; this->min = min; this->max = max; } const char* FelaktigtIndexUndantag::what() const throw() { ostringstream oss; oss << "Felaktigt index i en vektoroperation: " << i << " (ska vara " << min << ".." << max << ")"; return oss.str().c_str(); // Fel. Varför? } template class Vektor { private: T* lagring; int storlek; public: Vektor(int storlek); Vektor(const Vektor& original); ~Vektor(); const Vektor& operator=(const Vektor& original); T& Vektor::operator[](int i) throw (FelaktigtIndexUndantag); }; template Vektor::Vektor(int storlek) { this->storlek = storlek; lagring = new T[storlek]; } template Vektor::Vektor(const Vektor& original) { this->storlek = original.storlek; lagring = new T[original.storlek]; for (int i = 0; i < storlek; ++i) lagring[i] = original.lagring[i]; } template Vektor::~Vektor() { delete [] lagring; } template const Vektor& Vektor::operator=(const Vektor& original) { if (this != &original) { delete [] lagring; this->storlek = original.storlek; lagring = new T[original.storlek]; for (int i = 0; i < storlek; ++i) lagring[i] = original.lagring[i]; } return *this; } template T& Vektor::operator[](int i) throw (FelaktigtIndexUndantag) { if (lagring < 0 || i >= storlek) throw FelaktigtIndexUndantag(i, 0, storlek - 1); return lagring[i]; } int main() { try { Vektor v(3); v[0] = 7.1; v[2] = 5.9; cout << "v[0] == " << v[0] << endl; cout << "v[1] == " << v[1] << endl; v[7] = 1.17; } catch (FelaktigtIndexUndantag& e) { cout << "Det uppstod ett indexfel: " << e.what() << endl; throw; } catch (exception& e) { cout << "Det uppstod ett annat fel: " << e.what() << endl; throw; } catch (...) { cout << "Det uppstod ett annat fel." << endl; throw; } } Utmatning --------- v[0] == 7.1 v[1] == 0 Det uppstod ett indexfel: Felaktigt index i en vektoroperation: 7 (ska vara 0..2) terminate called after throwing an instance of 'FelaktigtIndexUndantag' what(): Felaktigt index i en vektoroperation: 7 (ska vara 0..2) Abort (core dumped) Utdrag ur vektor-7.cpp ---------------------- const char* FelaktigtIndexUndantag::what() const throw() { ostringstream oss; oss << "Felaktigt index i en vektoroperation: " << i << " (ska vara " << min << ".." << max << ")"; string s = oss.str(); const char* cs1 = s.c_str(); char* cs2 = new char[strlen(cs1) + 1]; strcpy(cs2, cs1); return cs2; } 6. Kort: Felhantering i standardbibliotekets vektorklass, std::vector ===================================================================== vektor-8.cpp ------------ #include #include using namespace std; int main() { try { vector v(3); // operator[]() -- Subscripting access without bounds checking v[0] = 7.1; v[2] = 5.9; cout << "v[0] == " << v[0] << endl; cout << "v[1] == " << v[1] << endl; v[7] = 1.17; cout << "v[19] == " << v[19] << endl; // at() -- Subscripting access with bounds checking v.at(1) = 77.3; cout << "v.at(0) == " << v.at(0) << endl; cout << "v.at(1) == " << v.at(1) << endl; cout << "v.at(19) == " << v.at(19) << endl; } catch (exception& e) { cout << "Det uppstod ett annat fel: " << e.what() << endl; // throw; } } Utmatning --------- v[0] == 7.1 v[1] == 0 v[19] == 0 v.at(0) == 7.1 v.at(1) == 77.3 Det uppstod ett annat fel: vector::_M_range_check