February 05, 2007
Practical C++ Error Handling in Hybrid EnvironmentsManaged Environments/Proprietary Error-Handling Mechanisms
Because Microsoft is the only vendor that provides deep integration to operating system facilities (SEH) or managed environments (COM and CLR) at the C++ level, all of the following mechanisms are Windows specific. This is an important trend. The Mono runtime brings .NET to the cross-platform world, and while it doesn't yet provide a C++ front-end, there is work towards it. All of these error-handling mechanisms integrate and interoperate with each other and with C++ exceptions to some degree.
Get/Set LastError is a glorified errno for Win32. It's better than errno in the sense that code in other languages can access it through the Windows API. Cross-language, as opposed to cross-platform, code is a big concern of Microsoft.
Win32 Structured Exception Handling (SEH) is the low-level OS mechanism that calls a certain callback when some thread faults. The Visual C++ compiler wraps it for you and implements the C++ exception model on top of it. You can ignore it most of the time, just know that it's there in the bizarre case you need to dig that deep and find out something.
COM/ATL error handling is a different beast. COM was designed to be language agnostic. As such it couldn't rely on any language's error-handling facilities. So, COM came up with its own error-handling facility. COM has an error object that is accessible via various COM interfaces: ICreateErrorInfo for creating error objects, IErrorInfo for accessing error information, and ISupportErrorInfo to check if a certain COM class actually creates instances of the error object. The punch-line is that I don't really know how ubiquitous the COM error object is. When I used COM/ATL a lot, I used plain HRESULTs, which is equivalent to C status/return codes.
In terms of managed (.NET/CLR) exceptions, the .NET/CLR assimilates, subsumes, and encapsulates all other Windows technologies. It provides its own exception model and specific CLR extensions for C++, but also has a unique mechanism for interfacing with pure C++ code. It reportedly just works most of the time. I've never used it. When Exceptions Are Not Used (And Why)
There are two metareasons for not using C++ exception handling:
C++ is the only language I'm aware of that even lets you turn exception handling on/off. In other languages, you either have exceptions or you don't. You can use other mechanisms if you want, but exceptions are there and runtime failures probably trigger exceptions, so you must be ready to catch them.
If you develop code that you want other people to use, you would be wise to consider exposing it through a C API. The C++ Application Binary Interface (ABI) is not part of the C++ Standard. Every compiler vendor changes it with every major version (and sometimes more often). The result is that you can't statically link a C++ library if it was compiled using a different compiler. It is also not possible on a nonWindows platform to load C++ classes directly from a dynamic library. So, if you are not in the business of shipping source distributions and you want to reach a larger audience than people who happen to have exactly your compiler, stick to C.
With managed environments, you don't even have the choice. If your code must run in a managed environment, you must play by its rules. There isn't much point in throwing an uncaught exception in your COM out of a process module. It would never make it across to the calling code without marshalling.
Legacy systems can be big, yet fragile, creatures. The smallest change might break them, they may rely on undocumented APIs or bugs in a specific compiler implementation. Upgrading the compiler or changing the error-handling strategy is typically prohibitively expansive. Chances are you won't be able to persuade the powers that be to upgrade the compiler and potentially destabilize the system just so you can use cool C++ exceptions in your shiny new module.
Exception handling has a runtime overhead, even when exceptions are not thrown. The compiler needs to manage a list of all the stack objects that were constructed to call their destructors when an exception is thrown. This implies at least a space overhead (if the list is prepared during compilation). If the preparation of this list is done at runtime, then there is also a time overhead. Many performance-critical systems have a shoestring budget for CPU cycles and memory bits, and developers can't afford the cost of exception handling.
In general, C++ developers, who are considered hard-core in comparison to other developer, are uncomfortable with C++ exception handling due to the complexity of implementing it correctly. Exception handling in other languages that support garbage collection (Java, Python, C#) is much simpler and developers have no problem using it.. Still, exceptions have a lot going for them. They can considerably simplify error handling in C++ programs. However, they are not simple by themselves. Many developers just know that getting C++ exceptions to work is complicated. Many experienced C++ developers never used exceptions in their code because they only worked on projects that fall under the aforementioned restrictions or they learned C++ before exceptions were introduced to the language. In the initial meeting of a new project when it's time to pick an error-handling strategy, inexperienced developers tend to stick to what they know. Exception handling is not as cool as templates, so developers often don't feel that they "have" to use it, just because they never used it before.
Software is complicated. When system architects face crucial decisions such as picking an error-handling strategy, they often prefer a single mechanism instead of two that have to interact. Since many libraries and existing code bases don't leverage exceptions, this single mechanism is often a simple return code, or one of the other nonexceptional mechanisms.
|
|
||||||||||||||||||||||||||||||
|
|
|
|