7

I have a function that can be reduced to this:

void f() {
    static MyObject o("hello");

    DoSomethingWith(o);
}

This function is called across a C API boundary, so like a good boy, I use try to catch any exceptions that are thrown before they cross the boundary and screw things up:

void f() {
    try {
        static MyObject o("hello");

        DoSomethingWith(o);
    } catch (const MyObjectException& e) {
        Message("Constructor of o failed");
    }
}

This function is called the first time and I get the message "Constructor of o failed". However, later, the function is called again, and I get the message again. I get the message as many times as f is called. I am using Visual C++ so this tells me what MSVC++ does, but not what should be done.

My question is, what should happen when the constructor of a static function variable terminates unusually (by throwing, a longjmp out of the constructor, termination of the thread that it's in, etc)? Also what should happen with any other static variables declared before and after it? I would appreciate any relevant quotes from the standard as well.

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249

3 Answers3

10

Section 6.7 ([stmt.dcl]) of the C++11 standard states that

The zero-initialization (8.5) of all block-scope variables with static storage duration (3.7.1) or thread storage duration (3.7.2) is performed before any other initialization takes place. Constant initialization (3.6.2) of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered. An implementation is permitted to perform early initialization of other block-scope variables with static or thread storage duration under the same conditions that an implementation is permitted to statically initialize a variable with static or thread storage duration in namespace scope (3.6.2). Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Sorry if it's apparent from that quote, but what if I call `TerminateThread` on the running thread in the constructor of the static object or something? – Seth Carnegie Jan 29 '12 at 23:18
  • @SethCarnegie: `TerminateThread` is bad mojo. I'll pretend you said `ExitThread`. – Ben Voigt Jan 29 '12 at 23:26
  • Either way, what should happen (or is it in the quote?)? The standard quote just says that if it exists _by throwing an exception_, it doesn't mention any other way. Again, I might be misreading. – Seth Carnegie Jan 29 '12 at 23:39
  • @Seth: No, you're not misreading. We're pondering if this is an underspecification in the standard and I'm currently formulating a follow-up question. – Xeo Jan 29 '12 at 23:44
  • @Seth: It depends on interpretation. Does the quote mean: "When the initialization exits by throwing an exception, the initialization is not complete" and "When the initialization is not complete, so it will be tried again the next time control enters the declaration.". Probably, because of the word "so". In that case, there's an unstated but valid argument "When the initialization exits by `longjmp` or `ExitThread`, the initialization is not complete." and "When the initialization is not complete, so it will be tried again the next time control enters the declaration.". – Ben Voigt Jan 29 '12 at 23:45
  • @Ben: The standard would use a semicolon instead of a comma after "If the initialization exits by throwing an exception". It does so for all listings, IIRC. As such, the sentence seems to directly apply when an exception is thrown. – Xeo Jan 29 '12 at 23:47
  • @Xeo. It undoubtedly applies when an exception is thrown. The question is what the consequent is. (A -> B -> C) -> (B -> C) -> ((D -> B) -> (D -> B -> C)) -> ((D -> B) -> (D -> C)) – Ben Voigt Jan 29 '12 at 23:50
  • Where: A = exception thrown; B = initialization is not complete; C = initialization is retried; D = ExitThread – Ben Voigt Jan 29 '12 at 23:51
2

Q: what should happen when the constructor of a static function variable terminates unusually [...] ?

A: §6.7 [stmt.dcl] p4
[...] Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.

So the initialization of o will be tried again if it exits by throwing an exception. I think the same applies to any kind of abnormal exit from the initialization, though it's not explicitly stated. Brb, looking for more quotes.

Since I couldn't find anything related, I opened a follow-up question.

Q: Also what should happen with any other static variables declared before and after it?

A: Nothing, as long as neither the thread or the whole program terminate.

§3.6.3 [basic.start.term]

Destructors (12.4) for initialized objects (that is, objects whose lifetime (3.8) has begun) with static storage duration are called as a result of returning from main and as a result of calling std::exit (18.5). Destructors for initialized objects with thread storage duration within a given thread are called as a result of returning from the initial function of that thread and as a result of that thread calling std::exit. The completions of the destructors for all initialized objects with thread storage duration within that thread are sequenced before the initiation of the destructors of any object with static storage duration.

§3.7.2 [basic.stc.thread]

A variable with thread storage duration shall be initialized before its first odr-use (3.2) and, if constructed, shall be destroyed on thread exit.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • This line probably doesn't *always* apply, but it often would: "The function signature `longjmp(jmp_buf jbuf, int val)` has more restricted behavior in this International Standard. A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any automatic objects." – Ben Voigt Jan 29 '12 at 23:14
  • @Ben: Doesn't apply here, no destructor is ever invoked if initialization fails. – Xeo Jan 29 '12 at 23:18
  • Might, if there are other locals in scope. – Ben Voigt Jan 29 '12 at 23:25
0

Redesign your program. Static variables will be attempted to be initialized until the initialization succeeds. If that pattern doesn't fit, you should find a better way to express your goal. Perhaps a static unique_ptr that you populate in a controlled environment? If you have a resource that you cannot reliably construct, you have to either relegate the construction into some other context where you can handle the error, or make your function depend only optionally on the resource (e.g. via a null pointer).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084