There is no question that C++ is an extremely complex programming language with a lot of traps that can catch even the most hardened coders by surprise.
One of my favorite language features that can bring a headache is the constructor member initializer list. Personally, I try not to use this feature unless necessary. But if you need to initialize a member of a reference type or a class type using a non-default constructor, this is your only choice. And that is when you open your code to a category of easy to introduce – hard to spot bugs.
Have you ever changed the order class members during routine refactoring? Touched only the header file and didn’t bother to look into the .cpp? Then you probably didn’t know about this.
Consider the following demonstration:
[gist https://gist.github.com/martinky/b13f1db0c750cdcf9365 /]
Looks perfectly normal, right? Especially if the class declaration and constructor implementation were separated into a header file and a .cpp source file. The order in which you write member initializers is completely irrelevant, only the member declaration order matters.
Now that is pretty logical and consistent. Looking at a class definition, one would expect that members are initialized in the order in which they are declared. But one tends to forget this rule when looking at a constructor implementation and the initializer list. Especially if this is in another file. Also consider destruction: members are destructed in the reverse order of their construction. If two constructors of would construct the class members in two different orders, in which order should the destructor destroy them?
In the example code above, the comp.a variable is initialized using an undefined value, which makes it effectively an uninitialized variable and therefore undefined behavior.
I don’t understand why C++ allows to write member initializers in different order, especially if it leads to such nasty bugs. In my opinion, this should be a hard compile error. Even worse, compilers are completely silent about it. At least by default.
Clang and g++ will only throw a warning when run with the -Wreorder or -Wall option.
The Visual C++ compiler (cl) is completely silent. It does not notice this bug even when using the highest warning level /W4, /Wall, not even using the venerated /analyze option!
Do not rely on your favorite compiler. Try to push your code through as many compilers as you can. Strive to get your code free of warnings on the highest warning levels on all of them. Add a static code analyzer to your arsenal. Preferably not just one, but as many as you can get your hands on. C++ developers who are locked to a single platform are at an inherent disadvantage because they might get deprived of quality tools that are available on other platforms.