0

I'm figuring out how to add C++ exception handling to deal with run-time errors in an existing real-time application. I'm starting with failures in the construction of objects that encapsulate drivers for components of the hardware system, for example a power supervisor microcontroller that sits on the SPI bus of the Raspberry Pi platform.

Following RAII principles, I must fully initialize these objects in their constructors, which leads to the possibility of failure if system resources are not available, for example if the SPI driver does not load. Since constructors don't have return values, I must deal with such failures using exceptions.

These hardware driver objects are static (file scope). Why? So that their constructors are called automatically and, more important, their destructors are called automatically on program exit. Also, I need to get to the objects from anywhere in the main program file since they represent global hardware resources.

I don't really care how the exception is handled (I can emit useful error information before the throw) but I do care that the program terminates properly.

What is happening is that if the constructor of a statically allocated object fails and throws an exception, then the destructors of other static objects that are lexically before the failing object are not called. I have tested this in a minimal test bed. Classes Apple, Pear and Orange have nothing but constructors and destructors that announce themselves to stdout, except that the constructor of Orange then throws an exception. In the main file, I define one static instance of Apple, Pear and Orange, in that order. The constructors are called on program execution, Orange throws its exception and the program ends without calling destructors for Apple and Pear.

What am I missing, here?

In the answers to similar questions, e.g. 556655, people suggest: - Not throwing exceptions in constructors. Huh? - Having a separate initialization method, called "manually" after construction, to do anything that might fail. So what about RAII? (This, by the way, is how I have things now, without exceptions). - Changing the static objects to pointers and "manually" invoking the constructors using the new operator. Then I have to manage calling the right destructors in the event of a failure, which I was hoping the use of exceptions would avoid. - Wrapping every static object inside another object which has an accessor function to get a reference to it. Apparently, the inner object's constructor will not be called until the first time that the accessor is called on the outer object, which would allow me to catch the exception and, presumably, this would result in a tidy exit. This seems like a horrible kludge.

Recall, I don't need to catch the exception, the program can terminate however it wants. This makes my question different from others that I have found. My question is, why aren't the destructors called for static objects that were successfully constructed?

Graham.

  • Here we go again. Global variables, static globals (next up: singletons - arrrgh; the pain, it hurts). If you want to avoid a ton of pain: don't use globals, not even file-scope static ones. Just *don't*. – Jesper Juhl Aug 06 '16 at 15:09
  • I understand your revulsion at the use of file scope variables. I do my best to avoid them but in this program I cannot figure out how to do that in any reasonable way. I have file-scope objects that encapsulate a read-only named pipe, a write-only named pipe and three drivers for hardware sub-systems. Commands arrive over one pipe and I manipulate the hardware to execute the command and generate a response over the other pipe. I have to keep track of the state of the hardware to fail commands that cannot be executed until some operation completes. – Graham Davies Aug 06 '16 at 16:23
  • [continued] ... If I don't make the driver objects global (or package them up in a global wrapper object, which I don't think solves the problem) I have to pass them through the hierarchy of command parsing and execution functions as arguments (unless I am missing something and have been for decades). – Graham Davies Aug 06 '16 at 16:23
  • @GrahamDavies: You could use global pointers instead of global objects, and initialize them in main. That will avoid the whole static initialization mess altogether. You'll still have the ugly globals but you can decide about that later :) – Chris Beck Aug 06 '16 at 16:49

1 Answers1

4

You're missing that the exception isn't handled. When the program terminates due to an unhandled exception, you're not guaranteed that destructors of local and static objects, are called (as I recall it's implementation defined). One solution is to put those objects in a common wrapper object, create it in a try block in main, and just make a pointer to it available for all that arbitrary access that maybe is there for the current globals.

Sketch:

class Drivers
{
friend auto main() -> int;
    // ...
};

namespace impl {
    Drivers* p_drivers;
}  // namespace impl

auto drivers() -> Drivers& { return *impl::p_drivers; }

auto main()
    -> int
{
    try
    {
        Drivers drivers;
        impl::p_drivers = &drivers;
        // ...
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        log_failure( x.what() );
    }
    return EXIT_FAILURE;
}
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • I dug out the standard quote in this answer: http://stackoverflow.com/questions/32323406/what-happens-if-a-constructor-throws-an-exception/32323458#32323458 – Chris Beck Aug 06 '16 at 16:51
  • "... the exception isn't handled." This is probably what I was missing. I have been mistaking what appears at stdout / stderr as handing the exception when in fact it is "implementation defined" behavior. I was particularly misled by the appearance of the string that I passed to the constructor of the exception at the throw. I figured that since my string appears there was the equivalent of an exception handler in the startup code that calls the constructors of the static objects. Now I see my mistake. – Graham Davies Aug 06 '16 at 18:05
  • So, it seems that RAII (and, as a consequence, exceptions in constructors) is incompatible with file-scope objects. Solutions all seem to be the creation of the objects in block scope, either using the new operator or automatically on entry into the block, and the initialization of one or more pointers in file scope to provide the universal access that I think I need. Packaging the file-scope objects together as "drivers" seems like a good idea, although it doesn't seem to me that it avoids the basic objection to file-scope objects (which is what, exactly?). – Graham Davies Aug 06 '16 at 18:15
  • @GrahamDavies: Correct summary. The problem with mutable file scope objects that are accessible to much code, is that they provide invisible and hard to find lines of communication and control. One can never be sure what parts of the code affect other parts, when something is guaranteed initialized, how reliable these values are (for where do they come from?). With these open communication facilities n pieces of code have n×(n-1)/2 lines of communication and control, i.e. O(n²) influences to consider. That's *complexity*. Which leads to bugs and much needless redundant work. – Cheers and hth. - Alf Aug 06 '16 at 18:24
  • Basic objection to file-scope objects : Yes, that's what I understand the objection to be; you can access them from a lot of places. So, passing them around as arguments is just as bad. If I can't reframe the problem I'm trying to solve so that I don't need to access my hardware drivers from all over the place, then having them as file-scope objects is as good as anything else and I just need to write the drivers so as to protect themselves from misuse and make efforts to make the code transparent and easy to verify. (I'm not finding any other posts that properly address this issue.) – Graham Davies Aug 06 '16 at 18:47