Posted inInformation Technology / Raspberry Pi

C++ 11 and Qt — Part 3 This is Broken

One of the many reasons medical devices and all systems where adverse outcome for humans are slow to adopt “new” things is that there is so little known about “the wrong way.” You will find many snippets and books on-line professing to show you “the write way” but so few show you the wrong way and explain why it is the wrong way. Most of the token few wrong way post and book examples you will find tend to lean pretty heavy on style and seemingly random coding standards.

For a number of years people have been pushing constructor initializer lists on C++ developers. I have heard many claims about them being “more efficient” but I’ve never seen hard evidence of that. It is very difficult to prove something is _always_ more efficient across a wide range of compilers and platforms. This is especially true on embedded targets where dynamic memory allocation is traditionally a slow and expensive process. To some extent I have adopted using the initializer list while coding, but, I probably shouldn’t have. From a readability standpoint it is much easier to determine initialization values if they are all in a list.

MyClass::MyClass( Widget *widget) :
  m_widget(widget),
  m_ratio( 3.75),
  m_width( 500),
  m_length( 500)
{
 ...
}

Given a constructor for a complex object which could be hundreds or thousands of lines long, especially in an embedded world where the object may well be controlling a physical device which has an initialization protocol, it is certainly easier to see what the values were set to if they are all at the top. In many ways this is feature is homage to the COBOL WORKING-STORAGE SECTION.

WORKING-STORAGE SECTION. 
    01 CONSTANTS. 
       05 KEY_PAD_MODE              PIC 9 COMP   VALUE 1. 
       05 SIGNAL_BELL               PIC 9 COMP   VALUE ZERO. 
       05 TCA_SIZE                  PIC 99 COMP  VALUE 12. 
       05 TCA_UNIT                  PIC 99 COMP  VALUE 2. 
       05 WORKSPACE_SIZE            PIC S9(9) COMP VALUE 2000. 
       05 LOGICAL_UNIT              PIC 9 COMP VALUE 1. 
       05 START-LINE                PIC 9 COMP VALUE 1. 
       05 SCREEN-LINE-COUNT         PIC 99 COMP VALUE 23. 
     
    01 STATUS-VARIABLES. 
       05 DONE-FLAG                 PIC X. 
          88 WE-ARE-DONE            VALUE 'Y'.

Before we started writing callable COBOL modules with LINKAGE SECTION parameters, we as programmers used to religiously assign every declared variable an initial value in this section. Once we started having re-entrant/callable modules, the practice changed to initializing everything in the initial code. Why? Because WORKING-STORAGE was only initialized on the first call. After that you had whatever you left there.

The initializer list is also misleading. Just because a member variable was initialized to a value in the list doesn’t mean the constructor didn’t change that value due to some requirement. Bad coding you say? Not in the least. In fact it is really common in the embedded and semi-embedded worlds. Devices have physical limitations. You are required to log any errors AND you are required to provide patient safety above all else.

What if your constructor is for a serial port? What if you defaulted to 115200 baud because all of the new devices contain high end serial ports? What if this code is loaded onto an older device where 9600 is the maximum hardware supported baud rate? What do you do? Do you throw an exception and hope something catches it? Not cool. Given most medical devices must be multi-lingual adding _any_ text error message is a painful process. The patient-safety-first approach is to log the error to the system log file so it is recorded for support, then fall back to 9600 inside of the constructor. The device isn’t broken, it is just communicating slower than the newer devices.

Constructors for objects which control physical devices will _always_ have need to change the values. Don’t like the serial port example? How about a system cooling fan with integrated/remote  thermometer? Your default value is an idle speed, say 300 RPM. Fan gets started because fan must _always_  be started before checking temp. Temperature is checked and found to be just below egg frying so fan gets set to maximum speed.

Think that is too much for a constructor? Perhaps. At least until you look at the system requirement stating all devices must be initialized to a known “safe” state. A device which is about to overheat and fail is not safe.

Using initializer lists with C++ constructors is a wee bit dangerous under C++ 11 standard. All initial values should be declared in the class definition. Any overrides should be done as assignments inside of the constructor. Why? Here is some horribly written broken code to show you just how wrong things can go.

// test2class.h
// 
#include <string>

class Test2Class
{
private:
        int m_kount = 10;
        double m_ratio = 3.45;
        std::string m_msg{ "default message"};

public:
        explicit Test2Class( int kount, double ratio, const std::string& msg ) :
                m_kount(kount),
                m_ratio(ratio),
                m_msg(msg) {}
        explicit Test2Class( const std::string& msg)  { Test2Class( m_kount, m_ratio, msg); }
        explicit Test2Class( double ratio) { Test2Class( m_kount, ratio, m_msg); }
        explicit Test2Class( int kount)    { Test2Class( kount, m_ratio, m_msg); }
        explicit Test2Class()  { }
        
        
        std::string dumpValues();
        
        
};

Assume you are a student new to C++. You see the language allows you to assign initial values in the class definition and you want to write something which both assigns default values while having constructors that allow a single value to be overridden. The above code, while not good, may seem like a reasonable option.

#include "test2class.h"
#include <sstream>
#include <iostream>

std::string Test2Class::dumpValues()
{
    std::string txt;
    std::stringstream txtStream( txt);
        
    txtStream << "Kount: " << m_kount 
              << " Ratio: " << m_ratio
              << " Msg: " << m_msg;
        std::cout << "txtStream before return: ";
        std::cout << txtStream.str() << std::endl;
    return txtStream.str();
}

The main module is not much different from our previous example.

#include "test2class.h"
#include <iostream>

int main()
{
        Test2Class a;
        Test2Class b( 16);
        Test2Class c( 3.1435);
        //Test2Class d( std::string{"new message"});
        Test2Class d( "new message");
        
        std::cout << "Values for a: " << a.dumpValues() << std::endl;
        std::cout << "Values for b: " << b.dumpValues() << std::endl;
        std::cout << "Values for c: " << c.dumpValues() << std::endl;
        std::cout << "Values for d: " << d.dumpValues() << std::endl;
}

Good luck debugging this!

roland@roland-HP-Compaq-8100-Elite-SFF-PC:~/fopa$ g++ -Wall -g -std=c++11 test2.cpp test2class.cpp -o test2
roland@roland-HP-Compaq-8100-Elite-SFF-PC:~/fopa$ ./test2txtStream before return: Kount: 10 Ratio: 3.45 Msg: default message
Values for a: Kount: 10 Ratio: 3.45 Msg: default message
txtStream before return: Kount: 10 Ratio: 3.45 Msg: default message
Values for b: Kount: 10 Ratio: 3.45 Msg: default message
txtStream before return: Kount: 10 Ratio: 3.45 Msg: default message
Values for c: Kount: 10 Ratio: 3.45 Msg: default message
txtStream before return: Kount: 10 Ratio: 3.45 Msg: default message
Values for d: Kount: 10 Ratio: 3.45 Msg: default message

I was completely flabbergasted this compiled. I thought for certain the compiler would gag on passing member variables to a delegated constructor. Part of the problem can be laid at the door of Mr. Strousup’s explanation.

If a member is initialized by both an in-class initializer and a constructor, only the constructor’s initialization is done (it “overrides” the default).


	class A {
	public:
		int a = 7;
	};

This is equivalent to:


	class A {
	public:
		int a;
		A() : a(7) {}
	};

The statement I’ve quoted is meant to protect a developer from the unmentioned damage assigning values within the class definition can do given the “equivalent to” example. The compiler is generating/modifying constructor initializer lists behind the scenes. Your only method of debugging will be stepping through the assembly code (you did take assembler in college, right?)

In theory, the initial quoted statement requires compiler developers to test for collision before jambing in the hidden initializers. I suspect some compilers simply generate their list and put it in front of any list which happens to be on the constructor assuming “last one in wins.” If the class members aren’t objects controlling physical devices which cannot be shared you “could” get away with this. What if the device being allocated is something like cancer treating radiation device and the initialization releases some default amount into the patient, followed by the amount the doctor actually ordered?

Changing the code of our previous example does appear to work using the initializer list.

#ifndef TEST3CLASS_H
#define TEST3CLASS_H

#include <string>

class Test3Class
{
public:
    ~Test3Class();
    
    explicit Test3Class();
    
    explicit Test3Class( const std::string& msg); 
    explicit Test3Class( double ratio);
    explicit Test3Class( int kount);
        
    std::string dumpValues();

private:
        int m_kount=10;
        double m_ratio=3.45;
        std::string m_msg{"default message"};
};


#endif // TEST3CLASS_H
#include "Test3Class.h"
#include <iostream>
#include <sstream>

Test3Class::Test3Class( int kount) :
  m_kount(kount)
{
    std::cout << "Constructor (int): kount: " << m_kount << " ratio: " << m_ratio << " msg: " << m_msg << std::endl;
}

Test3Class::Test3Class( double ratio) :
  m_ratio(ratio)
{
    std::cout << "Constructor (double): kount: " << m_kount << " ratio: " << m_ratio << " msg: " << m_msg << std::endl;
}

Test3Class::Test3Class( const std::string& msg) :
  m_msg(msg)
{
    std::cout << "Constructor (std::string): kount: " << m_kount << " ratio: " << m_ratio << " msg: " << m_msg << std::endl;
}

Test3Class::~Test3Class()
{
}


std::string Test3Class::dumpValues()
{
    std::string txt;
    std::stringstream txtStream( txt);
        
    txtStream << "Kount: " << m_kount 
              << " Ratio: " << m_ratio
              << " Msg: " << m_msg;
    return txtStream.str();
}
#include "Test3Class.h"

int main(int argc, char **argv)
{
        Test3Class b( 16);
        Test3Class c( 3.1435);
        Test3Class d( "new message");
        
        std::cout << "Values for b: " << b.dumpValues() << std::endl;
        std::cout << "Values for c: " << c.dumpValues() << std::endl;
        std::cout << "Values for d: " << d.dumpValues() << std::endl;
        
        return 0;
}
Constructor (int): kount: 16 ratio: 3.45 msg: default message
Constructor (double): kount: 10 ratio: 3.1435 msg: default message
Constructor (std::string): kount: 10 ratio: 3.45 msg: new message
Values for b: Kount: 16 Ratio: 3.45 Msg: default message
Values for c: Kount: 10 Ratio: 3.1435 Msg: default message
Values for d: Kount: 10 Ratio: 3.45 Msg: new message
Press ENTER to continue...

For the next post in this series I propose to write a C++ 11 test program you can use to determine if your compiler takes the “last one in wins” approach or really does selectively add.