Exception Handling in the Modern C++ Toolchain
Go Up to Exception Handling in C++Builder
The C++Builder toolchain supports multiple types of exception handling: normal C++ exceptions and Structured Exception Handling (SEH). Mixing them is unsafe and ineffective. Few, if any, compilers, including Microsoft and Clang, allow mixing them together in the same method. Our compiler follows the same approach.
You may have run into this by compiling code that was accepted by a previous version of our toolchain, and this time got an error like:
[bcc64x Error] MyFile.cpp(15): cannot use C++ 'try' in the same function as SEH '__try'
Read on for how to solve it (it's straightforward) plus information on why.
Contents
Background
C++ exceptions occur when you throw a C++ object (any type of object, including an int
). A C++ exception is handled using try
and catch
:
try {
// dodgy code here
throw new Ex();
} catch (Ex& e) {
// handle exception here
}
Structured Exception Handling (SEH) is not entirely a C++ concept; it has extra keywords added to C++. This kind of exception is raised via the RaiseException()
API (not throw) or for hardware or kernel exceptions. It is common to use __try/__finally
as follows:
__try {
// dangerous code here
} __finally {
// cleanup here
}
Both examples above are correct to use. The issue is mixing the two, such as shown in the example below:
try {
__try {
// do something potentially dangerous here
} __finally {
// you want this to run anyway
}
} catch(...) {
// handle exception here
}
It is almost impossible to support two different types of exception handling in the same “frame” (same method) using two control flow mechanisms, two triggers for unwinding objects, etc.
Remember, it is valid and supported to use both kinds of exception handling in your app, including in methods that call each other. The issue to avoid is trying to mix both in the same location.
This guide provides three suggestions to separate the exceptions without errors.
Deleting with __finally
Writing code using the __finally
keyword to handle resource cleanup is common, as shown in the code example below:
TMyClass* myclass = new MyClass();
__try {
try {
DoSomething();
} catch(...) {
MessageTheUser();
}
} __finally {
delete myclass;
}
It is possible to eliminate the __try/__finally
block using a smart pointer.
For example, using a unique_ptr
will cause the owned object to be deleted at the end of its scope, including if an exception is thrown.
unique_ptr
is a stack-allocated RAII object, meaning its destructor is called when the stack is unwound when handling exceptions.The above example now becomes the following code:
#include <memory> // Use Refactor > Add Include to add this automatically
auto myclass = std::make_unique<TMyClass>(5, L"hello"); // Pass construction params as normal
try {
DoSomething();
} catch(...) {
MessageTheUser();
}
The new example is much simpler to read, has less nesting, and is guaranteed safe by the language.
To keep it to a specific scope, add a block by adding {
} to make a scoped block where the unique_ptr will be deleted.
myclass->Foo()
, deference it (*myclass
), and get the actual pointer to pass it to a method by calling myclass.get()
. Although IMO passing a const std::unique_ptr
, using const&
is better; do not let the internal pointer be moved unless is intentional.Using try
and __finally
Since try
is C++ EH and __finally
is SEH, it is common to write code such as the example below, resulting in errors.
try {
DoSomething();
__finally {
Cleanup();
}
To workaround this issue, change the try
to a __try
, as follows:
__try {
DoSomething();
__finally {
Cleanup();
}
Split C++ and SEH Exception Handling
Splitting the two kinds of exception handling is necessary to simultaneously use C++ EH and SEH exceptions. Although both types of exception handling can be in the same code path, call stack, and app, they cannot be part of the same method.
To split the two types, one needs to take the inner part of a try/catch
or a __try/finally
block and put it in a new method. The following code demonstrates it:
TBitmap* Grid::Repaint(const int CellSizePx, const CellPainting painting) {
try {
__try {
const TSize BMPSize = BitmapSize(CellSizePx);
m_pBitmap->Width = BMPSize.cx;
m_pBitmap->Height = BMPSize.cy;
m_pBitmap->Clear(BackColor);
PaintCellFill(CellSizePx, painting);
PaintCellBorders(CellSizePx, painting);
} __finally {
return m_pBitmap.get();
}
} catch(std::exception& ex) {
return nullptr;
}
}
Moving the entire __try/__finally
pair is necessary to create a new method. Use the Refactor > Extract Method tool to extract the method.
Select the code and right-click the highlighted area. Choose Refactor>Extract Method and the result should look as follows:
The Extract Method separates a code block into a new method. This keeps precisely the same behavior of your code but allows both types of exception handling to coexist.