Posted inInformation Technology / Raspberry Pi / Thank You Sir May I Have Another

C++ 11 and Qt — Part 5 Constructor Initializer List Religion

Okay, we are going to cover this one last time. If you are new to C++ programming you probably don’t know the doo-doo storm I kicked off bringing the subject up earlier in this series of posts. There are a few things which will start a bar fight among programmers. The first is declaring a single editor as the greatest editor of all time. (Besides, you will always find one grizzled troll declaring vi to be the greatest editor and that is an editor from a time when programmers lived in caves and ate their young. Nasty doesn’t begin to describe it.) When it comes to C++ programmers, the constructor initializer list also kicks off such a fight.

Be careful what you declare as a shop coding standard.

When theory becomes mandated practice you have a real train wreck when the theory turns out wrong or the practice bad.

The theory behind the constructor initializer list being “more efficient” is a special case, not a general rule. Two things have to be true for that statement to be true.

  1. The objects being created must be complex
  2. The object copy constructor must be more efficient than the assignment operator.
  3. The copy constructor is more efficient than the combination of default initialization followed by the assignment operator.

If any of those are not true then the “more efficient” claim is untrue. For most C++ compilers most native C data types (int, float, char, etc.) are not initialized or are only initialized when compiling for debug. There is a mystic and complex set of rules for when a C++ compiler will initialize them. Too complex for the human mind to care about. You cannot go wrong believing “only initialized when compiled for debug” style of coding, but you can go wrong with so many other styles.

The previous paragraph is really why so many shops adopted an “every member variable in the initializer list” standard. Too many times native data types did not get initialized and bad things happened. When a class had only one constructor, it wasn’t a burden. As classes become more complex and were put to more uses more constructors existed.

Rather than fight, those of us long in the tooth who actually have code which has been in production more than a decade lobbied to change the C++ standard. Initializing member variables inside the class definition is the fruit of that labor. Initializing all member variables in the class definition and having the compiler auto generate constructor initializer lists always was the right way to do things. Now the programmer only need initialize what is passed into the constructor.

We need to discuss this “more efficient” claim a bit more. Consider the class below.

class SomeClass
{
public:
    int width;
    int height;
    float ratio;
};

Do you think the initializer for this class actually does anything when not compiled for debug? Probably not. In truth this class is really just a struct made up of native data types. What if this class is the data type for all member variables of another class? Does the initializer list actually do anything for the other class? Probably not. Yes, you could use this data type within some Template class which could require some kind of initialization, but, why argue about the last 6 inches of an 8 lane wide highway? Yes, there will be purists who demand every class be responsible for initializing all of its members, I agree, so here:

struct SomeStruct
{
    int width;
    int height;
    float ratio;
};

Not everything you interface with will be C++. A good many device interfaces will use native data types because they are coded in C or assembler or are pure hardware { anyone remember calling outp() and inp() to control opto-22 relays?} A good many devices require you to send data structures with byte alignment. Despite that being an interesting discussion topic for some later post, the fact remains that native data types are not initialized in many cases.

The procedural fix to a design problem (i.e. mandating hand coded initializer lists for every constructor which initialize ever variable) is now heavily entrenched. It is in formal written coding standards for many shops and decades of code were written to that standard. When the code has to be modified by a newer junior member it is highly probably they could miss adding a new initializer to every constructor.

What remains to be seen is if these shops will modify both their formal written coding standards and all of their legacy code when switching to C++ 11. Besides being drudge work for a summer intern, they may be legal barriers. Changing code even nominally for some devices can require another round of formal external testing, think medical devices and other high risk systems.

Yes, there were reasons for what people did. Yes, it solved one problem and created others. Yes, an industry “best practice” wasn’t a good practice it was just the best we had at the time and it created other problems years down the road. Yes, when switching to C++ 11 all of this legacy code should be updated, but, as long as it compiles it most likely won’t be. There are still OpenSource C modules which are part of the Linux world written to the K&R coding standard. Until they don’t compile or need to be rewritten maintainers most likely won’t change them. Anyone remember writing pre-ISO C code like this:

int main(argc, argv)
    int   argc;
    char  *argv[];
{
    ...
}

I certainly do. I was twenty-something when the push came for full function prototyping.

int main(int argc, char *argv[])
{
    ...
}

We as developers and businesses tend to focus on today’s problem instead of the future problem. Various rationalizations are used to justify our focus. Here are some common ones:

  1. storage is too small/expensive
  2. memory is too small/expensive
  3. I’ll be dead by then so why should I care?
  4. This system is only temporary.

The last 2 are my favorites. I once worked on a “temporary” stock exchange trading floor system which was originally developed on a PDP-11, then migrated to the VAX and finally to the Alpha CPU based DEC computers. For those of you unfamiliar with the DEC line, PDP was 70s era (what UNIX was originally written on) VAX was the 80s era and Alpha the 90s. Do you know when the “temporary” life of that system finally ended? When they got rid of the trading floor and became a purely electronic exchange.

Y2K happened because of 1, 2 and 3. By the time storage started getting cheap in the early 90s the “best practice” created in the 70s was too entrenched to change until it finally had to be in the late 90s. Because of 1, 2 and 3 you younglings also have a 2036 and 2038 problem to look forward to. Oh, and you had best brush up on COBOL, FORTRAN and a few other original programming languages because the “cheap” Y2K fix chosen by far too many companies was to modify date routines to ass-u-me two digit years below 27 had a century of 20 while everything above that was 19. Of course, now that humans aren’t allowed to chain smoke at work or drive home hammered drunk, that “cheap” fix doesn’t work for a growing segment of the population living past 100. At least OpenVMS created a quadword time good through 31-Jul-31086 02:48:05.47 GMT. For those of you who want to know why November 17, 1858 was chosen as its base here is some interesting reading. There is a truly cosmic reason behind it.

Your “temporary” system will live on long after you are dead. Your offspring and the offspring of your family members will be saddled with cleaning up your messes. No matter how expensive you think storage and memory are, they are still cheaper than sweeping up multiple decades of mistakes. There is still K&R style C code in production systems and probably will be until UNIX/Linux fade from use. There will still be shops where “best practices” require you to hand code constructor initializer lists long after we are all dead and code they have written will still be in production long after we are all dead.

All of this is true, but that doesn’t mean you should add to the problem.