Bezpieczeństwo wyjątków w C++: OpenGL Krzysztof Czaiński Opiekun pracy: dr inż. Cezary Stępień Krzysztof Czaiński
Motywacja Dużo literatury o OpenGL The Red Book (Addison-Wesley) http://nehe.gamedev.net/ Dobrze opisane bezpieczeństwo wyjątków w C++ Exceptional C++, Herb Sutter OpenGL i wyjątki? GLT ( http://www.nigels.com/glt/ ) Building an OpenGL C++ Class, Dale Rogerson www.gaylesbiantimes.com/ Krzysztof Czaiński
Plan prezentacji Dlaczego wyjątki? Bezpieczeństwo wyjątków w C++ Inne sposoby traktowania błędów Bezpieczeństwo wyjątków w C++ Gdzie wstawić bloki try / catch, czy coś więcej? OpenGL, a wyjątki Więcej o bezpieczeństwie wyjątków Krzysztof Czaiński
Obsługa błędów, czyli problem komunikacji Autor klasy (biblioteki) może wykryć błąd, ale nie wie, co z nim zrobić Użytkownik klasy (biblioteki) wie, co zrobić z błędem, lecz nie potrafi go wykryć Przykłąd: Vector, odwołanie do nieistniejącego elementu. Krzysztof Czaiński Źródło: Materiały ZPR dr inż. Robert Nowaka
Sposoby traktowania błędów Wady Ignorowanie Zakończenie programu Komunikat dla użytkownika Kod powrotu Zmienna globalna Specjalny stan obiektu Rezerwacja zwracanej wartości Użytkownik może zignorować błąd Kod obsługi wymieszany z innym kodem Obiekt globalny – potencjalne źródło kłopotów Krzysztof Czaiński Źródło: Materiały ZPR dr inż. Robert Nowaka
Mechanizm wyjątków a zasoby void funkcja() { A* a = NULL; try{a = new A; // Kod rzucający delete a; } catch ( ... ) { delete a; throw; } class A {/*...*/}; void funkcja() { A* a = new A; // Kod rzucający delete a; } Niewygodne, nieczytelne, 2xnieefektywne Krzysztof Czaiński
Zdobywanie zasobów jest inicjalizacją (RAII) Konstrukcja obiektu jest połączona ze zdobyciem zasobu Destrukcja obiektu jest połączona ze zwolnieniem zasobu Gdy obiekt jest lokalny, zasób jest zwalniany automatycznie w momencie niszczenia obiektu Krzysztof Czaiński
Sprytny wskaźnik template< typename X > class AutoPtr { public: explicit AutoPtr( X* p = NULL ) : p_( p ) {} ~AutoPtr() { delete p_; } X& operator*() const { return *p_; } X* operator->() const { return p_; } private: X* p_; }; Możemy używać jak zwykłego wskaźnika. Uwaga: wszystkie metody throw() Krzysztof Czaiński
Użycie sprytnego wskaźnika class A {/*...*/}; void funkcja() { AutoPtr<A> a( new A ); // Kod rzucający a->metoda(); AutoPtr<A> b = a; // ??? } Co z kopiowaniem? Jak widać, bezpieczeństwo wyjątków jest czymś więcej, niż „gdzie wstawić bloki try/catch. Wręcz „jak unikać bloków try/catch”. Krzysztof Czaiński
Przykładowy kod z OpenGL void render() { double v[3]; glBegin( GL_TRIANGLES ); for ( int i = 0 ; i < n ; ++i ) getVertex( i, v ); // rzuca? glVertex3dv( v ); } glEnd(); Krzysztof Czaiński
Automatyczny glBegin/glEnd #include <boost/utility.hpp> : boost::noncopyable struct Begin { explicit Begin( GLenum mode ) glBegin( mode ); } ~Begin() glEnd(); }; Pytanie: jak zrobić niekopiowalność? Krzysztof Czaiński
Przykładowy kod z bezpiecznymi wyjątkami void render() { double v[3]; Begin( GL_TRIANGLES ); for ( int i = 0 ; i < n ; ++i ) getVertex( i, v ); // rzuca? OK. glVertex3dv( v ); } uważać na Begin(); Krzysztof Czaiński
Przykładowy kod z bezpiecznymi wyjątkami (2) void render() { double v[3]; Begin begin( GL_TRIANGLES ); for ( int i = 0 ; i < n ; ++i ) getVertex( i, v ); // rzuca? OK. glVertex3dv( v ); } Krzysztof Czaiński
Zachowanie stanu OpenGL-a void select() { glRenderMode( GL_SELECT ); // kod rzucający int hits = glRenderMode( GL_RENDER ); //... } Chcemy zagwarantować, żeby po wywołaniu select() OpenGL pozostał w trybie GL_RENDER Krzysztof Czaiński
Automatyczny glRenderMode class RenderMode : boost::noncopyable { public: RenderMode( GLenum mode, GLenum restoreMode ) : restoreMode_(restoreMode), restored_(false) { glRenderMode( mode ); } ~RenderMode() { restoreRenderMode(); } GLint restoreRenderMode() { GLint result = 0; if ( ! restored_ ) { restored_ = true; result = glRenderMode( restoreMode_ ); } return result; private: GLenum restoreMode_; bool restored_; }; Krzysztof Czaiński
Zachowanie stanu OpenGL-a z auto- glRenderMode void select() { RenderMode x( GL_SELECT, GL_RENDER ); // kod rzucający int hits = x.restoreRenderMode(); //... } Krzysztof Czaiński
Co to znaczy bezpieczeństwo wyjątków? Gwarancje bezpieczeństwa wyjątków wg Dave’a Abrahama: Podstawowa: Brak wycieku zasobów Wszystkie obiekty pozostają w stanie używalnym, ale nie koniecznie przewidywalnym Silna: Gdy wystąpi wyjątek, stan programu pozostaje niezmieniony Nothrow: Nigdy nie rzuca Czyli po prostu „there are no bugs” - Herb Sutter. Np. Container::AppendElements() global commit-or-rollback semantics Niektóre funkcje muszą zapewniać, aby umożliwić powyższe gwarancje. Np. Destruktory, operacje dealokacji. Operacje auto_ptr są nothrow. Która w przypadku kodu z OpenGL? Krzysztof Czaiński Źródło: GotW #61
Korzyści Programowanie bezpieczne z uwzględnieniem wyjątków ma istotne zalety: Użytkownik klasy (biblioteki) może używać mechanizmu wyjątków Wyjątki nie zakłócą pracy programu W razie niespodziewanego błędu (np. braku pamięci) program może działać dalej Wszelkie zasoby zostają zwolnione mimo wyjątku Unikamy nie zwolnienia zasobów przez nieuwagę Zasoby są zwalniane automatycznie . Eclipse i OutOfMemoryException glBeginList i glEnd Krzysztof Czaiński
Bezpieczeństwo wyjątków a etap projektowania template < typename T > class Stack { public: T pop(); // ... private: T* tab_; size_t size_; }; template < typename T > void Stack<T>::pop( T& result ) { // assert( size_ > 0 ); result = tab_[size_-1]; --size_; } template < typename T > T Stack<T>::pop() { // assert( size_ > 0 ); T result( tab_[size_-1] ); --size_; return result; } template < typename T > T Stack<T>::pop() { // assert( size_ > 0 ); return tab_[--size_]; } Krzysztof Czaiński
Podsumowanie Warto pisać kod w C++ uwzględniając wyjątki i warto uwzględnić to już na etapie projektowania. Dla zainteresowanych polecam: ZPR, dr inż. Robert Nowak http://www.gotw.ca/gotw/ 1. Nie jak dokumentacja, dopisywana później. Krzysztof Czaiński