Sven Johannsen 16.10.2013 C++ User Group Meeting NRW |
sven@sven-johannsen.de |
throwReport an exceptional contextif (parameter > 30) { throw exception_type(); } |
Exception typeclass exception_type {}; |
catchHandle an exceptiontry { some_code(15); } catch (exception_type& e) { // report error } catch (...) { // report error } |
throw std::exception("My Error text"); throw MyNamespace::MyOwnException(15, filename); throw 3; // int throw "My oyther error"; // const char* throw new CMemoryException; // CMemoryException* (MFC)
try { // try block } catch(excetion_type1 ex) { // catch block 1 } catch(excetion_type2& ex) { // catch block 2 } catch(...) { // catch block 3 }
void simple_example(int i) { try { if (i % 2) throw i; } catch(int ex) { cerr << "Don't call example() with odd numbers (i= " << ex << ")\n"; } }
// UI Function: Print user summery void OnButtonPressed() { try { PrintUserSummery(); // (1) } catch (exception& ex) { ReportError(ex.what()); } }
// process: Print user summery void PrintUserSummery() { string user_name = getCurrentUser(); unique_ptrpUserInfo = getUserInfo(user_name); // (2) PrintSummery(pUserInfo); }
unique_ptrgetUserInfo(string user_name) { unique_ptr pUserInfo(new UserInfo); DBRecInfo* pInfo = searchUserInfoInDB(user_name.c_str()); CopyDBInfoIntoUserInfo(pUserInfo, pInfo); ValidateUser(pUserInfo); // (3) string log_text = pUserInfo->name_ + " : " + to_string(pUserInfo->id_); LOG(log_text); delete pInfo; return pUserInfo; }
void ValidateUser(const unique_ptr& pUserInfo) { if (pUserInfo->id_ < 100) { throw std::exception("Don't report internal User"); } }
struct A { }; struct B : A { }; struct C : A { }; try { throw B(); } catch(C&) { // ... } catch(B) { // ... } catch(A&) { // ... }
void b() { try { c(42); // may throw } catch (std::exception& ex) { cout << "inner catch " << ex.what() << endl; throw; // "re"throw the exception } } void a() { try { b(); } catch (std::exception& ex) { cout <<"Outer catch: " << ex.what() << endl; } }
C++ calls the destructor only from initialized objects
class MyClass : public BaseClass { string name_; char* info_; public: MyClass(const string& name) : BaseClass(name), info_(new char[80]), name_(name) { strcpy(info_, name.c_str()); someCode(); // may throw } virtual ~MyClass() { delete[] info_; } };
After the throw
:
vectormySuperDooperFunc(const MyBitmap& bmp, const list & colors);
class MySuperDooperException { public: // ... }; // may throw MySuperDooperException vectormySuperDooperFunc(const MyBitmap& bmp, const list & colors) { // ... throw MySuperDooperException(param1, param2); }
Exceptions are (hidden) parts of the interface!
DBRecInfo* pInfo = searchUserInfoInDB(user_name.c_str());
DBRecInfo* pInfo = 0; int err = searchUserInfoInDB(user_name.c_str(), &pInfo); if(err != 0) return ERR_USER_INFO_NOT_FOUND; // continue ...
Nullptr, NaN or optional
boost::optional example:
optional<char> get_async_input() { if ( !queue.empty() ) return optional<char>(queue.top()); else return optional<char>(); // uninitialized } void receive_async_message() { optional<char> rcv ; // The safe boolean conversion from 'rcv' is used here. while ( (rcv = get_async_input()) && !timeout() ) output(*rcv); }
Use exceptions to report an error from a constructor, but be aware of the fully constructed objects problem.
the "good path"
MyObject obj(param1, param1); obj.DoSomeWork(); obj.DoMoreWork(); obj.DoSomeMoreWork(); |
combine "good path" from the "bad path"
MyObject obj(param1, param1); int err = obj.DoSomeWork(); if(err != 0) { handleError(err, obj); return 15; } err = obj.DoMoreWork(); if(err != 0) { handleOtherError(err, obj); return err; } err = obj.DoSomeMoreWork(); |
Example MVC: (same for multitier)
How to report the errors to the user?
Example without error handling from http://www.cplusplus.com/reference.
char sentence []="Rudolph is 12 years old"; char str [20]; int i; sscanf (sentence,"%s %*s %d",str,&i);
First example in the lexical_cast documentation.
try { args.push_back(lexical_cast<short>(*argv)); } catch(bad_lexical_cast &) { args.push_back(0); }
C++ Exceptions are designed for reporting errors which prevent the program from continuing.
negative example:
int main(int argc, char * argv[]) { using boost::lexical_cast; using boost::bad_lexical_cast; std::vector<short> args; while(*++argv) { try { args.push_back(lexical_cast<short> (*argv)); } catch(bad_lexical_cast &) { args.push_back(0); } } // ... }
example:
int main(char* argv[], int argc) { try { checkProgramOptions(argv); } catch (boost::bad_lexical_cast& e) { cout << "MyProgram: invalid parameter!" << endl; printUsage(); return 1; } // continue... }
Exception safety: http://en.wikipedia.org/wiki/Exception_safety
(David Abrahams)
RAII not only for smart pointers:
Caller | Callee | throw | |
---|---|---|---|
C++ function | -> | C++ function | OK |
C++ function | -> | C function | N/A |
C function | -> | C++ function | not OK |
If a callee throws an exception, the caller must be able to handle the exception!
How to call C++ function from other languages? (e.g. C)
Should be easy to solve:
#includeJNIEXPORT jstring JNICALL Java_Hello_getMessage(JNIEnv* env, jclass self) { return env->NewStringUTF("Hello from C++"); } // ...
Most examples don't show error handling...
... but, if the C++-Code throws an exception, the Java runtime creates ugly error messages:
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_UNCAUGHT_CXX_EXCEPTION (0xe06d7363) at pc=0x000007fefd22940d, pid=5 944, tid=5236
#
# JRE version: 6.0_37-b06
# Java VM: Java HotSpot(TM) 64-Bit Server VM (20.12-b01 mixed mode windows-amd64
compressed oops)
# Problematic frame:
# C [KERNELBASE.dll+0x940d]
#
...
and lengthy log files.
JNIEXPORT jstring JNICALL Java_Hello_getMessage(JNIEnv * env, jclass self) { try { std::string name = getCPPName("Hallo"); return env->NewStringUTF(name.c_str()); } catch(MyJniException& ex) { env->ExceptionDescribe(); env->ExceptionClear(); // (1) Search the (java) exception class jclass newExcClass = env->FindClass("MyException"); if(newExcClass) { // (2) Create a new instance of the exception and throw in the VM (later) env->ThrowNew(newExcClass, ex.what()); env->DeleteLocalRef(newExcClass); // (3) free the native reference }} // missing else! return nullptr; // (4) return a default value }
JNIEXPORT jstring JNICALL Java_Hello_getMessage(JNIEnv * env, jclass self) { try { std::string name = getCPPName("Hallo"); return env->NewStringUTF(name.c_str()); } catch(MyJniException& ex) { ex.ThrowJavaException(env); } return nullptr; }
see: ADB Plugin
Windows programming without Qt, MFC, ...
// define the windows handler LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // register the windows class WNDCLASSEX wcex = {}; wcex.cbSize = sizeof(WNDCLASSEX); // ... wcex.lpfnWndProc = WndProc; wcex.lpszClassName = "MyWindowClass"; return RegisterClassEx(&wcex); // create the window hWnd = CreateWindow("MyWindowClass", szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { try { switch (message) { case WM_PAINT: CppPaintFunction(hWnd); case WM_COMMAND: CppNotifyHandler(hWnd, message, wParam, lParam); break; // ... default: return DefWindowProc(hWnd, message, wParam, lParam); } } catch(std::exception& ex) { // correct exception type? // ??? return 0; }}
Qt, MFC or wxWidget programs look like pure C++ program, but with poorly exception support.
The system boundary between the C++ and the C callback is hidden in the frameworks.
#include <QTextEdit> class MdiChild : public QTextEdit { Q_OBJECT public: MdiChild(); void newFile(); bool loadFile(const QString &fileName); // ... protected: void closeEvent(QCloseEvent* event); // exceptions not allowed here private slots: void documentWasModified();// exceptions not allowed here // ... };
overwrite the application
class MyApplication : public QApplication { public: MyApplication(int argc, char* argv[]) : QApplication(argc, argv) {} private: virtual bool notify(QObject* receiver, QEvent* e) { try { return QApplication::notify(receiver, e); } catch (std::exception& ex) { QMessageBox(QMessageBox::Critical, "Error", ex.what()).exec(); // std::terminate(); return false; } } };
overwrite every MFC window
BOOL MyDialog::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { try { return CDialogEx::OnWndMsg(message, wParam, lParam, pResult); } catch(std::exception& ex) { MessageBox(CString(ex.what()), _T("Error"), MB_ICONERROR); return FALSE; } } // alternatively overwrite: // virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
Application is exception safe:
Use the exception filter to report your error.
(Side note: Don't use this in dialogs)
Application is not exception safe:
Terminate your application! (after logging the error)
No question about expected exceptions, but:
Demo
Absolutely, Positively, NEVER EVER Use catch(...)
The catch(...) construct has been very good to my bank account because it has caused more bugs in people's code then you can ever imagine. ...
-- John Robbins: Debugging Applications for MS .NET and MS Windows
but:
What is a good error message for catch(...)
?
try { // Some code... } catch(MyException& ex) { MessageBox(ex.what(), L"Error", MB_ICONERROR); } catch(...) { MessageBox(???, L"Error", MB_ICONERROR); }
invalid vector<T> subscript
bad allocation
bad lexical cast: source type value could not be interpreted as target
Some programmers think to know which error may occur.
try { someFunction(); } catch(...) { cerr << "Failed to open file \"XYZ.txt\""; }
Code will change over time. In the future someFunction();
will read from a database or another file.
catch(...)
with an empty catch block!try { someFunction(); } catch(...) { }
How to test someFunction();
?
Usual intention: Supress an exception and let the program continuing.
catch(...)
, because of the lack of control!Many catch(...)
blocks around of some suspicious code.
try {} catch(...) {}
conglomerationstry {} catch(...) {}
logicOlder versions of VC++ (older then VS 2005/ VC8) caught SEH exceptions with catch(...)
.
Ensure to use /EHsc
(only C++ Exceptions)
use catch(...)
to move an exception to a different context:
void test1() { try { thread t([](){ throw exception("fun with threads"); }); t.join(); } catch(exception& ex) { cout << "Exception: " << ex.what() << endl; } }
void test2() { std::exception_ptr err; thread t([&err](){ try { throw std::exception("fun with threads"); } catch(...) { err = std::current_exception(); } }); t.join(); try { if (err != std::exception_ptr()) std::rethrow_exception(err); } catch(exception& ex) { cout << "Exception: " << ex.what() << endl; } }
Write a message to a log-file. Or call Aunt Tilda. But do not throw an exception!
void trouble_dtor() { try { bad_object bad; foo(); } catch(A& ex) { cout << "B\n"; } catch(B& ex) { cout << "C\n"; } } |
struct A { }; struct B { }; void foo() { throw A(); } class bad_object { public: ~bad_object() { throw B(); } }; |
Copy all information into the exception!
Objects may get destroyed during the stack unwinding - so pointer and reference may get invalid.
Makes the UI code (and code in System boundaries) much simpler!
The UI code knows only one exception type:
Translate the error specific exceptions into unified exception type in the business logic.
Maybe it's necessary to translate an exception between layers (tiers).
#define THROW_MY_EXCEPTION(error_text) throwMyException(__LINE__, __FILE__ , \ __FUNCTION__ , error_text) void throwMyException(int lineno, const char* fname, const char* func_name, const string& errortext) { cerr << "MyException :" << endl << "File : " << fname << endl << "Functionname : " << func_name << endl << "Line : " << lineno << endl; throw MyException(errortext); } // ... THROW_MY_EXCEPTION("My error text!");
Config pConfig = nullptr; try{ pConfig = loadUserConfig(); } catch(...) {} try{ if (!pConfig) pConfig = loadProjectConfig(); } catch(...) {} try{ if (!pConfig) pConfig = loadDefaultConfig(); } catch(...) {}
if
statements for decisions. catch exceptions from constructor's initializer
// from stackoverflow struct B { B() { /*might throw*/ } }; struct A : B { A() try : B() { // ... } catch (...) { // handle exceptions thrown from inside A() or by B() // implicit throw; } };
not only for constructors?
int f() try { ... } catch(Error &e) { }
Please don't use this in production code.
Itanium C++ ABI: Exception Handling http://mentorembedded.github.io/cxx-abi/abi-eh.html
C++ exceptions under the hood appendix III: RTTI and exceptions orthogonality http://monoinfinito.wordpress.com/category/programming/c/exceptions/ Low level view of exceptions
Compiler Internals: Exceptions and RTTI http://www.hexblog.com/wp-content/uploads/2012/06/Recon-2012-Skochinsky-Compiler-Internals.pdf (Compare Win32, Win64 and GCC exception implementations)
Exception from the dotNet point of view http://www.codeproject.com/Articles/550510/Exception-Handling-and-NET. Different approch to classify exceptions (Level 1-3: user error, business logic, application crash)
/
#