The constructors of globally declared classes are invoked before main is entered. While this can be confusing to a new reader of the code because it is done so infrequently, is it necessarily a bad idea?
It's not necessarily a bad idea, but usually, yes.
First, it's global data, and globals are usually a bad thing. The more global state you have, the harder it becomes to reason about your program.
Second, C++ doesn't guarantee intialization order of static objects defined in different translation units (.cpp files) -- so if they depend on one another, you might be in trouble.
In addition to being of questionable form - It will not be portable to some platforms.
As you point out, it is allowed. In the last company I worked at, when such a situation would arise, we made it policy to add an appropriate comment to main() to indicate for which variables this applied to. If you have a bad situation, try to make the best of it.
Yes, this is bad. Since you will have no way to catch exceptions and deal with them, the default handler will be used. In C++ that means calling terminate...
Example: contents a.cpp
#include <stdexcept>
int f() { throw std::runtime_error("boom"); return 42; }
int i = f();
int main(int argc, char* argv[])
{
return 0;
}
Output of : g++ a.cpp && ./a.out
terminate called after throwing an instance of 'std::runtime_error'
what(): boom
Aborted (core dumped)
You can try add a try ... catch
in your main, that won't help.
Edit: Jalf's points are valid too. Listen to his advice.
Using global/static objects with non-trivial constructors and destructors is awful. I saw enough huge software projects that found themselves in a catastrophe because of an uncontrolled use of global/static objects and singletones.
The problem is not the fact that it's the code that is run outside the main
. It's that those objects are constructed and destroyed in an uncontrollable order.
In addition I believe it's a generally bad practice to use global variables anyway (even ordinary variables), with some exceptions. Every piece of code should be run within a well-defined context, and all the variables should belong to it.
It's not bad form at all, and it's not confusing. Static initialization is a deliberate feature of the language. Use it when you need to. Same with globals. Use them when you need to. Like any feature, knowing when it is appropriate and knowing its limitations is part of being a strong programmer.
Bad form is restructuring an otherwise perfectly fine program just to avoid globals or static initialization.
I will have to go so far as to say it is bad form for non-PODs, especially if you are working in a team primarily because of initialization order problems that can easily arise from this.
// the following invokes undefined behavior.
static bool success=cout<<"hello world\n";
People generally don't write code like above, but consider if success was initialized to the return value of another function. Now anyone tempted to use cout or cerr or any other global object in that function has invoked the same undefined behavior.
For user-defined types with constructors and destructors, consider the alternative method where access is initialization:
static Foo& safe_static()
{
static Foo f;
return f;
}
Unfortunately this also has problems with thread safety so a lock mechanism of some sort is required for the construction of 'f' if you are accessing safe_static concurrently.
That said, I do think one should try to keep things simple. Unfortunately when it comes to user-defined objects defined at file scope, it's far too easy to run into undefined behavior. The little extra effort required to write something like safe_static above can prevent a lot of headaches.
Exceptions are another point. If your static objects throw out of their constructor, then you have no way to catch the exception. If you want your application to be really robust and even handle startup errors, you'll have to structure your code carefully (ex: have try/catch blocks in the constructors for the objects you create at file scope so that the exception is not thrown outside of the ctor and also avoid initializer lists that throw).
If you're working in a team, you might think to yourself, "Oh, I'm not accessing any other globals in my class, I might as well make it a simple global with internal linkage at file scope." That might true then, but before you know it, your co-worker adds another global variable and tries to access it from the constructor of your class. Suddenly you have undefined behavior which might not even show up as a problem on the main platform you are targeting only for your code to crash and do other odd things when you try to port your code elsewhere.
It's really not worth the potential headaches IMO, and it's a problem that's much easier to avoid than to fix.