Tip #5: Split out C++ EH and SEH exception handling
After three separate variants of tip #4, something completely different!
Exception handling. There are multiple types and mixing them is A Bad Thing. Don’t mix C++ exception handling and SEH exception handling. It’s (a) not safe and (b) won’t work. But even better, we have a tool to help you separate them.
What does this mean?
C++ exceptions are when you throw a C++ object (which can be any type, even an int.) C++ exception handling is try-catch.
[crayon-677b4d3620de1346869840/]
Structured Exception Handling (SEH) is not a purely C++ concept, but has extra keywords added to C++ in order to support it. This is the kind of exception raised via the RaiseException() API (ie not throw) or for some hardware or kernel exceptions. You most likely don’t have a SEH handling mechanism (__try-__filter()) but you very likely may be using __try/__finally.
[crayon-677b4d3620de8365585791/]
Both these are completely fine to use. In fact, we reworked our SEH significantly in 12.2 and believe it works very well.
The issue is when you mix the two, such as:
[crayon-677b4d3620dea060541066/]
This code has a SEH handling mechanism directly inside the C++ EH handling mechanism. You could mix the other way around too. The point is: two EH mechanisms in the same code.
Why is this bad? Codegen. It is almost impossible to support two different types of exception handling in the same “frame” (same method, think of it as a call stack entry), using two control flow mechanisms, two triggers for unwinding objects, etc. (For an example of the kind of craziness, imagine — this isn’t quite it but is close in terms of expressing the craziness of how it could work — having one method that had various returns, but also a bunch of gotos; you could goto inside loops and goto inside other logic structures; then for fun you added other control flow (say, setjmp/longjmp) to mix with gotos, so execution was moving around all over the place using different systems for moving; and then you had to ensure that code was correct, including invoking all scope-exit behaviour like destructors. This may give a sense of the kind of codegen that multiple exception handling and control flow mechanisms can create.) Visual C++ doesn’t support it, and neither do we.
But in the past you could write this code and the compiler would let you do it. Now, it will give you an error. That may appear as though previously working code now doesn’t work, but that’s not the case: it’s very likely the codegen for that old code was horrendously wrong and resulted in exception memory leaks, may have had issues calling destructors in the right order or at all, etc. It’s a case of: wrong, but you may not have known it.
Note: A big theme of C++Builder 12.2 (see all the versions of tip 4) are that the new compiler can make previously dangerous code safer. We’ve used it for this purpose ourselves!
How can I fix it?
Ok, so you might be convinced that mixing two different types of exception handling in the same frame is crazy and asking for trouble, and you should never do it. But you might still have code you need to fix.
Three suggestions, one of which will make your code shorter, one which requires two keypresses, and a longer one — but where we have a tool to do it for you!
Many thanks to one of our customers (hello!) who emailed me with some extra suggestions here. I updated this post Dec 12, 2024.
Are you just deleting something?
Do you have a ‘__finally’ that deletes?
[crayon-677b4d3620deb191171621/]
You can get rid of the __try/__finally block completely by using a smart pointer. Here, use unique_ptr. It will be deleted at the end of its scope, and that includes 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 code becomes:
[crayon-677b4d3620df0949062823/]
It’s a lot simpler to read (less nesting) and is guaranteed safe by the language.
If you want to keep it to a specific scope, you can always add a block by adding in { } to make a scoped block where the unique_ptr will be deleted.
Crash course on using these: You can call a method on a unique pointer as you normally do (ie, myclass->Foo() ), dereference it (*myclass), and get the actual pointer if you need to pass it to a method by calling myclass.get() (although IMO passing a const std::unique_ptr const& is better; don’t let the internal pointer be moved unless you mean it.) Do not delete the pointer manually or you will double-delete. The unique pointer always deletes what it owns.
Are you using ‘try’ and ‘__finally’?
It’s really common to have code that looks like this:
[crayon-677b4d3620df1322137990/]
This will trigger the same error, because ‘try’ is C++ EH and ‘__finally’ is SEH. The solution is simple: change ‘try’ to ‘__try’. Ie, just add two underscores.
Are you using exception handling for exception handling?
In other words, are you balidly using both C++ EH and SEH at the same time, for good reasons?
If so, you need to split the two kinds of exception handling. Both types of exception handling can live in the same app just fine, and live in the same code path and call stack, just not in the same method. So splitting means: taking the inner part of a try/catch, or __try/finally, block and putting it in a new method.
Let’s take this messy code (adapted for the purposes of demonstration only, please don’t judge me):
[crayon-677b4d3620df2206741352/]
Pop quiz: suppose an exception is thrown in the __try block (say, setting the Width property gives an access violation.) What is returned: m_pBitmap.get(), or nullptr?
To clean this up, we want to move the entire __try/__finally pair into its own method.
We have a tool to do this for you: simply select the code, right-click, choose Refactor, and then Extract Method:
We added this refactoring in C++Builder 12.2 specifically for this use case. Extract Method takes a block of code and pulls it out into a new method. It is smart and will handle variables, create parameters in the extracted method, make them reference if required, etc.
So if you see this compiler error, you know what to do: select the code, Refactor, Extract Method. Then continue on! This once-off maintenance will solve bugs you may not even have known you had, plus, it makes exception handling reliable.