02. 02. 2009 Kurs: Programmierung in C/C++ Programmierung in C/C++ Philipp Lucas [email protected] 02. 02. 2009 Philipp Lucas, CDL, UdS 1 02. 02. 2009 Kurs: Programmierung in C/C++ Heute ◮ Exceptions ◮ Namespaces Philipp Lucas, CDL, UdS 2 02. 02. 2009 Kurs: Programmierung in C/C++ Exceptions Ursprung: Abfangen von außergewöhnlichen Fällen (Ausnahmen) Technisch: Kontrollflußänderung zu dynamisch vorhergehender Stelle Praktisch: Herausspringen bei Problemen aus tiefer dynamischer Struktur zu zu deren Behandlung geeigneter Stelle Besonderheiten: ◮ Ort von try und catch ◮ Wurfobjekte ◮ Interaktion mit anderen Sprachelementen (Konstruktoren, Kontrollfluß,. . . ) ◮ Verhalten bei Fehlern (ungefangene Exceptions) ◮ Deklaration von möglichen Würfen Philipp Lucas, CDL, UdS 3 02. 02. 2009 Kurs: Programmierung in C/C++ Motivation (1) bool open_file(){ if(!file_open("filename")) return false; ... } bool write_data(){ if(!file_open()) return false; ... } bool save_state(){ if(!write_data()) return false; ... } bool exit_program(){ if(!save_state()) return false; ... } int main(){ ... if(!exit_program()){ std::cerr << "An error occured." << std::endl; } } Philipp Lucas, CDL, UdS 4 02. 02. 2009 Motivation void open_file(){ if(!file_open("filename")) throw Exception("File open error."); ... } void write_data(){ file_open(); ... } void save_state(){ write_data(); ... } void exit_program(){ save_state(); ... } int main(){ ... try{ exit_program(); } catch(Exception e){ ... } } Philipp Lucas, CDL, UdS Kurs: Programmierung in C/C++ (2) 5 02. 02. 2009 Kurs: Programmierung in C/C++ Kontrollfluss normal: _^]\ XYZ[ g ' Exception: _^]\ XYZ[ c PQRS / WVUT f f ' g PQRS WVUT g g PQRS WVUT h PQRS / WVUT h Gilt für Funktionen wie auch für Blöcke Philipp Lucas, CDL, UdS 6 02. 02. 2009 Kurs: Programmierung in C/C++ Einfache Struktur Werfen: void print(const Person* const person){ if(person==NULL) throw Exception("Unspecified argument in print_all."); ... } Fangen: try { print_header(); print(selection); print_footer(); } catch (Exception e){ std::cerr << "Error: " << e.error_text(); } Philipp Lucas, CDL, UdS 7 02. 02. 2009 Kurs: Programmierung in C/C++ Prinzipien try-catch-Sequenz: Exceptions, die während der Ausführung des try-Blockes geworfen werden, können von der catch-Sequenz behandelt werden. throw-Ausdruck: Objekt (Argument von throw) wird erzeugt, die Kontrolle geht zum Anfang des dynamisch nächsthöheren, passenden catch-Blockes. Auf dem Weg dahin werden die lokalen Variablen zerstört, wie beim normalen Ende von Blöcken oder Funktionen. Philipp Lucas, CDL, UdS 8 02. 02. 2009 Kurs: Programmierung in C/C++ Beispiel try{ func1(); MyClass something; throw Exception(); func2(); } catch(Exception e){ ... } Kontrolle geht from throw direkt zum catch-Block: func2 wird nicht aufgerufen. Entsprechendes bei Würfen tiefer innerhalb des try-Blockes. something wird als lokale Variable zerstört, wie beim Ende des Blockes. Philipp Lucas, CDL, UdS 9 02. 02. 2009 Kurs: Programmierung in C/C++ Wurfobjekte In C++ kann alles geworfen werden, was kopiert werden kann; es gibt keine dafür nötige, speziell ausgezeichnete Oberklasse Exception o. ä. ◮ throw new FileAccessException("settings file"); ◮ throw FileAccessException("settings file"); ◮ throw "Access denied on settings file.\n"; ◮ throw 34; Bibliotheksklassen: Später. Philipp Lucas, CDL, UdS 10 02. 02. 2009 Kurs: Programmierung in C/C++ Fangen von Exceptions try { /* ... */ catch(Typ1 o1){ catch(Typ2 o2){ catch(...) { /* } /* ... */ } /* ... */ } ... */ } ◮ Es wird der erste Catch-Block aufgerufen, dessen Parametertyp zur Exception passt ◮ Geworfenes wird kopiert ◮ Es reicht Angabe des Typs, wenn das Objekt selbst nicht gebraucht wird (wie bei Funktionen) ◮ ...: Alles wird gefangen Philipp Lucas, CDL, UdS 11 02. 02. 2009 Kurs: Programmierung in C/C++ Lokale Variablen Lokale Variablen werden zwischen Wurf und Fangen (stack unwinding) freigegeben: Destruktoren werden aufgerufen. Problem: Dynamischer Speicher, sonstige Ressourcen: try { int* temp = new int[128]; Aclass aobj(); fill(temp); do_something(aobj,temp); delete[] temp; } catch(...){ std::cerr << "Something bad happened.\n"; } Wenn fill oder do something eine Exception wirft (und nicht vorher selbst temp freigibt), dann ist der Speicherplatz, auf den temp zeigt, verloren. Philipp Lucas, CDL, UdS 12 02. 02. 2009 Kurs: Programmierung in C/C++ Ressourcenmanagement try { int* temp = new int[128]; Aclass aobj(); fill(temp); do_something(aobj,temp); delete[] temp; } catch(...){ /* ... */ } ◮ Destruktor der lokalen Variablen aobj wird aufgerufen: aobj kann ihren Speicherplatz aufräumen ◮ temp (der int*) wird auch freigegeben, nicht jedoch der Speicherplatz, auf den temp zeigt. ◮ Gleiches Problem bei anderen Ressource: Dateihandlern, GUI-Handlern, . . . ◮ Lösung: Ressourcenmanagement in Klassen, deren Objekte als lokale Variablen deklariert werden. Philipp Lucas, CDL, UdS 13 02. 02. 2009 Kurs: Programmierung in C/C++ Ressourcenmanagement (2) try { std::vector<int> temp(128); FileHandle handle("log.txt"); fill(temp); do_something(temp, handle); } catch(...){ /* ... */ } ◮ temp und handle sind lokale Variablen, die freigegeben werden. ◮ Im Destruktor von std::vector wird der Speicherplatz für das beinhaltete Array freigegeben. ◮ Im Destruktor von FileHandle schliesst man die geöffnete Datei. ◮ Allgemein: Es werden die Destruktoren aller Objekte aufgerufen, die zwischen Eintritt in den try-Block und dem Auftritt des throw lokal angelegt wurden: Diese Destruktoren müssen für das Ressourcenmanagement sorgen. Philipp Lucas, CDL, UdS 14 02. 02. 2009 Kurs: Programmierung in C/C++ Ausnahmen in Konstruktoren Was passiert, wenn während eines Konstruktors ein Fehler auftritt? FileHandle("log.txt");: Datei lasse sich nicht öffnen Fehlermeldung: ◮ Rückgabewert: Geht nicht. ◮ Statusvariable: Geht, Benutzung unelegant. ◮ Exceptions: Ermöglichen die Fehlermeldung auf angemessene Weise. Philipp Lucas, CDL, UdS 15 02. 02. 2009 Kurs: Programmierung in C/C++ Funktions-Exceptions Was passiert bei Ausnahmen in der Initialisierungsliste? unsigned int get_id(){ FileHandle file("log"); file.write("Object created.\n"); file.close(); return ++max_id; } A::A() : _id(get_id()) { /* ... */ } Exception beim Öffnen der Datei wird durch den Konstruktor zu der deklarierenden Funktion geworfen. Philipp Lucas, CDL, UdS 16 02. 02. 2009 Kurs: Programmierung in C/C++ Funktions-Exceptions Behandlung im Konstruktor selbst: Spezialfall der function exception A::A() try : _id(get_id()) { /* ... */ } catch(FileException){ std::cerr << "Could not open log file.\n"; _id = 0; } void f() try { ... } catch (...) { ... } Falls in Konstruktor/Destruktor: Gleiche Exception wird automatisch wiedergeworfen. Philipp Lucas, CDL, UdS 17 02. 02. 2009 Kurs: Programmierung in C/C++ Spezifikation von Exceptions funcdecl throw(Exceptionlist): Spezifikation aller potentiell herausgeworfenen Exceptions funcdecl throw(): Zusicherung, daß nichts herausgeworfen wird. ◮ Angabe nur von Exceptions, die herausgeworfen werden: Was intern behandelt wird, ist egal. ◮ Oberklassen gelten für alle Unterklassen. ◮ Keine Angabe: Alles darf herausgeworfen werden. ◮ Exception-Spezifikation muß bei allen Deklarationen und Definitionen wiederholt werden. ◮ Virtuelle Funktionen in Unterklassen: Nur Spezialisierung ist erlaubt (Verringerung der Möglichkeiten). ◮ Konsistenz der Spezifikationen wird zur Compilezeit überprüft; Konsistenz von geworfenen Exceptions mit Spezifikation wird zur Laufzeit überprüft. Philipp Lucas, CDL, UdS 18 02. 02. 2009 Kurs: Programmierung in C/C++ Beispiel class Collection { ... virtual void add_item(const Item*) throw (OutOfMemoryEx, InvalidEx) // Wirft hoechstens diese beiden Exceptions oder Untertypen davon. virtual unsigned int size() const throw(); // Wirft gar keine Exception. }; class SpecialCollection : Collection{ ... virtual void add_item(const Item*) throw (OutOfMemoryEx) ; // Weniger als in Collection: Darf. virtual unsigned int size() const; // Mehr als in Collection: Darf nicht. }; Philipp Lucas, CDL, UdS 19 02. 02. 2009 Kurs: Programmierung in C/C++ Wiederwerfen throw; ohne Argument: Gefangene Exception wiederwerfen. void g(){ throw "Exception."; } void f(){ int* a; try{ a = new int[21]; g(a); delete[] a; } catch(...){ delete[] a; throw; } } Philipp Lucas, CDL, UdS 20 02. 02. 2009 Kurs: Programmierung in C/C++ Exceptions in der Standardbibliothek exception iSSSS SSSS SSSS SSS k5 kkk kkk k k kk kkk logic error runtime errors KS KS length error/domain error/ out of range/invalid argument range error/ overflow error/underflow error exception eeee2 eeeeee jjjjj5 e e e e e jjj eeeeee jjjj eeeeee e j e j e e j e eeeeee bad alloc bad exception O iSSkXSXXXXXXXXX SSS SSS XXXXXXXXXX SSS XXXXX SS XXXXX XXX ios base::failure bad typeid bad cast Allgemeines Rahmenwerk zur Organisation von Exceptions. Philipp Lucas, CDL, UdS 21 02. 02. 2009 Kurs: Programmierung in C/C++ Bibliotheks-Exception #include <exception> class exception{ public: ... virtual const char* what() const; }; Alle Standardausnahmen erben von exception. Philipp Lucas, CDL, UdS 22 02. 02. 2009 Kurs: Programmierung in C/C++ Wichtige Bibliotheks-Exceptions ◮ std::vector::at() und std::string::at() werfen out of range Geprüfte Varianten von operator[](). ◮ new wirft bad alloc, wenn nicht genügend Speicherplatz da ist. Dies kommt teilweise heraus, z. B. bei STL-Containern. ◮ ios wirft ios base::failure bei spezifizierten Übergängen nach bad(), eof() oder fail() std::cout.exceptions(ios base::badbit | ios base::failbit) Philipp Lucas, CDL, UdS 23 02. 02. 2009 Kurs: Programmierung in C/C++ Verhalten bei dynamischen Problemen Hier nur zwei Beispiele: ◮ Funktion wirft Ausnahme, die nicht in der deklarierten Ausnahmeliste steht: std::unexpected() wird aufgerufen; deren Verhalten kann spezifiziert werden (set unexpected(handler function)) ◮ Ausnahme wird nicht gefangen: std::terminate() wird aufgerufen; deren Verhalten kann spezifiziert werden (set terminate(handler function)) Philipp Lucas, CDL, UdS 24 02. 02. 2009 Kurs: Programmierung in C/C++ Sonderfälle Sonderfälle wie: ◮ Exception im Destruktor einer automatischen Variablen ◮ Exception im Destruktor einer static Variablen nach dem Ende der mainFunktion ◮ Exception im Copy-Konstruktor der temporären Variable während des Fangens der Exception ◮ std::bad exception-Exception im Handler für undeklariert geworfenen Exceptions → nicht in dieser Vorlesung, aber wichtig beim Behandeln von Exceptions in größeren Projekten. Philipp Lucas, CDL, UdS 25 02. 02. 2009 Kurs: Programmierung in C/C++ Weiteres ◮ Exceptions sind ein Mechanismus für den internen Programmablauf: Es sind keine Signale. ◮ Exceptions müssen keine Fehler signalisieren: Man kann Exceptions als nichtlokale return-Anweisungen oder tiefere break ansehen (Ausbruch aus geschachtelter Schleife). ◮ Bei Exceptions, die Fehler signalisieren, kann man nicht in allen Fällen beim Fangen den Fehler beheben: Programmabbruch, aber gesteuert. Philipp Lucas, CDL, UdS 26 02. 02. 2009 Kurs: Programmierung in C/C++ Namespaces Namespace: Optionale benannte Region ◮ Zugriff auf Namespace-Mitglieder wie bei Klassen mit :: ◮ Sinn: Auseinanderhalten von separatem Code, keine Nameclashes, Interfaces ◮ Können geschachtelt werden ◮ Import-ähnliche Direktiven Bekannter Namespace: std Allgemein: Achtung bei älteren Compiler-Versionen. Philipp Lucas, CDL, UdS 27 02. 02. 2009 Kurs: Programmierung in C/C++ Namespaces namespace GUI { class Window{ public: Window(const unsigned int size_x, const unsigned int size_y); enum vis_t { NO, WINDOW, FULL }; void set_visibility(const vis_t vis); ... }; const char version[]; } GUI::Window app_win(600,400); window.set_visibility(GUI::Window::WINDOW); std::cout << "Using version " << GUI::version << ’.’ << std::endl; Philipp Lucas, CDL, UdS 28 02. 02. 2009 Kurs: Programmierung in C/C++ Namespaces (2) Schachtelung, globaler Namespace: namespace A{ int f(int); namespace B{ void g(int){ f(6); } } } void f(int); namespace B { void f(int); void g(){ A::B::g(A::f(7)); f(8); ::f(12); } } Philipp Lucas, CDL, UdS 29 02. 02. 2009 Kurs: Programmierung in C/C++ Namespace-Erweiterungen ◮ Namespaces können geschachtelt werden (siehe Beispiel) ◮ Namespace-Aliases: namespace Lib = VendorLibrary v3 2a; ◮ Namespaces können erweitert werden (im Gegensatz zu Klassen). Technik: ⊲ Header: Namespace-Deklarationen mit Interface-Typen, -Funktionen etc. ⊲ Implementierung: Namespace-Deklaration erweitert mit für die Implementierung nötigen Klassen etc. std darf i. W. nicht erweitert werden. Philipp Lucas, CDL, UdS 30 02. 02. 2009 Kurs: Programmierung in C/C++ Deklaration/Definition namespace A{ void f(); // Deklaration } void A::f() { } // Definition namespace B{ void f(); // Deklaration } namespace B{ void ff() { } // Definition einer anderen Funktion } Philipp Lucas, CDL, UdS 31 02. 02. 2009 Kurs: Programmierung in C/C++ using-Deklarationen Import bestimmter Bezeichner (using-Deklarationen): using qualifizierte ID; Bezeichner ist nun ohne Qualifikation innerhalb des aktuellen Scopes sichtbar. Beispiel: using std::cout; using std::endl; cout << std::width(6) << 45 << endl; Achtung: namespace A { enum Colour { Green, Yellow, Red }; } using A::Colour; Colour ist importiert, nicht jedoch Green etc. Philipp Lucas, CDL, UdS 32 02. 02. 2009 Kurs: Programmierung in C/C++ using-Direktiven Import aller zur Verfügung stehenden Bezeichner (using-Direktiven): using namespace NS; Die Bezeichner in NS sind nun ohne Qualifikation innerhalb des aktuellen Scopes sichtbar. Beispiel: using namespace std; cout << width(6) << 45 << endl; Es können zusätzliche Namenskollisionen auftauchen: void f(const char* const string){ string var = string; } Philipp Lucas, CDL, UdS 33 02. 02. 2009 Kurs: Programmierung in C/C++ using-Spezifikationen Sichbarkeitsregeln für using-Spezifikationen (Direktiven oder Deklarationen): ◮ Überladene Funktionen werden in allen Varianten durch Deklarationen importiert. ◮ Aber: Sichtbarkeit zum Zeitpunkt von using: namespace A { using A::f; namespace A { ... f(1.0); // A::f(1.0); // void f(int); } void f(double); } Ruft int-Funktion auf. Ruft double-Funktion auf. ◮ Doppeldeklaration durch using-Direktive: Nur wo erlaubt Philipp Lucas, CDL, UdS 34 02. 02. 2009 Kurs: Programmierung in C/C++ using-Spezifikationen (2) ◮ Explizite Deklarationen (normal oder mit using) sind sichtbarer als durch Direktiven importierte Namen. ◮ Schachtelung: Sichtbarkeit wird herausgeführt (transitiv): namespace A { void f() {} } namespace B { using namespace A; using namespace B; ... g(); B::f(); f(); void g() {} } ◮ using von Methoden: Hier nicht. Philipp Lucas, CDL, UdS 35 02. 02. 2009 Kurs: Programmierung in C/C++ Argument dependent lookup Argument-dependent name lookup: Idee: Wenn Argumente einer Funktion in einem bestimmten Namespace deklariert sind, so sollte man die Funktion auch dort suchen. namespace A{ enum enum_t { E }; void f(int) { } void f(enum_t) { } } int main(){ const A::enum_t e = A::E; A::f(7); f(e); } Philipp Lucas, CDL, UdS 36 02. 02. 2009 Kurs: Programmierung in C/C++ Unnamed Namespaces namespace { void f() {} } Unbenannter Namensraum: Deklaration äquivalent zu namespace EINDEUTIGE_ID{ void f() {} } using EINDEUTIGE_ID; Effekt: ◮ benutzbar innerhalb der Datei, aber nicht außerhalb ◮ C++-Alternative zu static Philipp Lucas, CDL, UdS 37 02. 02. 2009 Kurs: Programmierung in C/C++ Ausblick ◮ Diverses Philipp Lucas, CDL, UdS 38