Exceptions in C++

Sven Johannsen
16.10.2013
C++ User Group Meeting NRW

sven@sven-johannsen.de
www.sven-johannsen.de
@svenjohannsen

Content

Background

Syntax

throw
Report an exceptional context
if (parameter > 30)
{
  throw exception_type();
}
Exception type
class exception_type {};
catch
Handle an exception
try
{
  some_code(15);
}
catch (exception_type& e)
{
  // report error
}
catch (...)
{
  // report error
}

Syntax

Exception type / throw

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)

Syntax

try/catch

try {
  // try block
}
catch(excetion_type1 ex) {
  // catch block 1
}
catch(excetion_type2& ex) {
  // catch block 2
}
catch(...) {
  //  catch block 3
}

Syntax

simple example

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";
  }
}

Syntax

more realistic example (a)

// UI Function: Print user summery
void OnButtonPressed()
{
  try
  {
    PrintUserSummery(); // (1)
  }
  catch (exception& ex)
  {
    ReportError(ex.what());
  }
}

(skip)

Syntax

more realistic example (b)

// process: Print user summery
void PrintUserSummery()
{
  string user_name = getCurrentUser();
  unique_ptr pUserInfo = getUserInfo(user_name); // (2)
  PrintSummery(pUserInfo);
}

Syntax

more realistic example (c)

unique_ptr getUserInfo(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;
}

Syntax

more realistic example (d)

void ValidateUser(const unique_ptr& pUserInfo)
{
  if (pUserInfo->id_ < 100)
  {
    throw std::exception("Don't report internal User");
  }
}

2 Pillars of Exception handling

destructors

catch blocks

Details

the order matters

struct A { }; 
struct B : A { }; 
struct C : A { };

try {
  throw B();
}
catch(C&) {
    // ...
}
catch(B) {
    // ...
}
catch(A&) {
    // ...
}

Details

throw; // exception forwarding

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;
  }
}

Details

fully constructed objects

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_; 
    }
  };

Execution Flow

Throwing an exception: "reverses" the execution direction

Execution flow

After the throw:

Interface

SuperDooper.h
vector mySuperDooperFunc(const MyBitmap& bmp, const list& colors);
SuperDooper.cpp
class MySuperDooperException
{
public:
  // ...
};

// may throw MySuperDooperException
vector mySuperDooperFunc(const MyBitmap& bmp, const list& colors)
{  // ...
    throw MySuperDooperException(param1, param2);
}

Exceptions are (hidden) parts of the interface!

Alternatives

C++

Operating System

Alternatives

return values

from "more realistic example"

Exceptions

DBRecInfo* pInfo = searchUserInfoInDB(user_name.c_str());

Error codes

DBRecInfo* pInfo = 0;
int err = searchUserInfoInDB(user_name.c_str(), &pInfo);
if(err != 0)
    return ERR_USER_INFO_NOT_FOUND;
// continue ...

Alternatives

invalid object state

Nullptr, NaN or optional as valid function return value.

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);
}

Why use exceptions?

Constructor Failures

How to report an error from a constructor?

Use exceptions to report an error from a constructor, but be aware of the fully constructed objects problem.

Separate the "good path" from the "bad path"

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();

Separate Error Handling and Reporting Code

Example MVC: (same for multitier)

MVC

How to report the errors to the user?

Don't Suppress Errors

It's easy to ignore return values (error code), but it's hard to ignore exceptions

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);
}

Prevent the Program from Continuing

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);
        }
    } // ...    
}

Prevent the Program from Continuing

Use exceptions to handle exceptional situations.

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

Exception safety: http://en.wikipedia.org/wiki/Exception_safety

(David Abrahams)

Basic exception safety

RAII not only for smart pointers:

System boundaries

Exceptions are language features

CallerCalleethrow
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!

System boundaries

Calling C++ from other languages

How to call C++ function from other languages? (e.g. C)

System boundaries

Plugins & Language extensions

Should be easy to solve:

System boundaries

Simple JNI example

#include 

JNIEXPORT jstring JNICALL Java_Hello_getMessage(JNIEnv* env, jclass self)
{
  return env->NewStringUTF("Hello from C++");
}
// ...

Most examples don't show error handling...

System boundaries

... 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.

System boundaries

JNI example with exceptions

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
}

System boundaries

JNI example with exceptions

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;  
}

System boundaries

Total Commander Example

see: ADB Plugin

System boundaries

Callbacks

Windows handler

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);

Windows handler

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;
}}

UI Frameworks

C++ UI frameworks try to hide the C windows interface from the (C++) developer.

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.

UI Frameworks & System boundaries

UI Frameworks

No clean boundaries between C++ and UI Framework code
#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
  // ...    
};

Exception filter

Qt Example

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;
    }
  }
};

Exception filter

MFC Example

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);

Exception filter

How to handle exceptions in the exception filter?

to catch or not to catch

How to handle unexpected exceptions?

No question about expected exceptions, but:

Demo

catch(...) is evil

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

catch(...) is evil

catch(...) is an Anti-Pattern

but:

No Message for User

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);
}

Don't report std::excetion.what() to the user!

Don't report errors like:
Don't expose internal information
But very useful for logging!

tight coupling

tight coupling between (expected) Exception and Handler

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.

Hide error from testers

Never use catch(...) with an empty catch block!
try
{
  someFunction();  
}
catch(...) { }

How to test someFunction();?

Usual intention: Supress an exception and let the program continuing.

Angst catch

Many catch(...) blocks around of some suspicious code.

1. Search for try {} catch(...) {} conglomerations

2. Fix the root problem

3. Remove the try {} catch(...) {} logic

Don't Try to Catch an Access Violation

Older versions of VC++ (older then VS 2005/ VC8) caught SEH exceptions with catch(...).

Ensure to use /EHsc (only C++ Exceptions)

context switch (C++11)

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;
  }  
}

context switch (C++11)

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;
  }  
}

Idioms & Pattern

Don't throw in destructors

from the C++FAQ

[17.9] How can I handle a destructor that fails?
Write a message to a log-file. Or call Aunt Tilda. But do not throw an exception!

Don't throw in destructors

Example

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();
  }
};

Don't store pointer and references in Exceptions

Copy all information into the exception!

Objects may get destroyed during the stack unwinding - so pointer and reference may get invalid.

Execution flow

One Exception to report errors messages

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.

Support Multitier architecture

Split vertical

Support Multitier architecture

Maybe it's necessary to translate an exception between layers (tiers).

MACRO plus function to throw

Throw with the help of a MARCO and one single function:
#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!");

Don't Use Exceptions for Flow Control

Config pConfig = nullptr;
try{
  pConfig = loadUserConfig();
} catch(...) {}

try{
  if (!pConfig) pConfig = loadProjectConfig();
} catch(...) {}

try{
  if (!pConfig) pConfig = loadDefaultConfig();
} catch(...) {}

Bonus: function-try-block

N2356

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;
  }
};

function-try-block

not only for constructors?

int f()
try
{
   ...
}
catch(Error &e)
{
}

Please don't use this in production code.

Links

Questions

Questions?



/

#