Posted inExperience / Information Technology

C++ Initialization and Constructors Explanation

Chicago construction crane

While many of the tools I and other developers use making medical devices require C++17 much of the code we write is C++11. Usually this is mandated by the shop. There are good reasons for sticking with C++11. It is easier to change out a library, especially if another project has already vetted that version of the library with the FDA, than it is to change the coding standard of an existing device.

Instead of a full program as many of the other Example/Explanation posts on C++, I’m going to use snippets from my RedDiamond OpenSource project. I have been loath to mentally train myself on many of the newer C++17 features because in my wold I cannot use most of them. That is beginning to change. While lambdas were and are still a damned dangerous C++11 “feature,” some of the other C++17 changes really will make code easier to maintain and safer.

Constructors

Please take a look at the top of this class.

class FileDetails
{
public:
    FileDetails();

    FileDetails( const FileDetails &other );
    FileDetails( const QString fileName );
    FileDetails( const QString fileName, const QString cacheFileName, const QString lexerName,
                 const QString tabText, int eolMode, sptr_t pos );
    FileDetails( const QString fileName, const QString cacheFileName, const QString lexerName,
                 const QString tabText, int eolMode, sptr_t pos, sptr_t lineNumber );

    static FileDetails fromString( const QString &str );

    FileDetails &operator=( const FileDetails &other );

This is forced on you when you are using a C++11 coding style. Yes, non-static native types like int and those which are synonyms for them like sptr_t could be initialized in the class declaration, but class types could not be. Initializing only some of the variables in the constructor and the rest in the class declaration caused a lot of confusion and coding errors.

Default values are a blessing. Default values are a curse. Many of you would have condensed this down to

class FileDetails
{
public:
    FileDetails();

    FileDetails( const FileDetails &other );
    FileDetails( const QString fileName, const QString cacheFileName="", 
                 const QString lexerName="",
                 const QString tabText="", int eolMode=0, sptr_t pos=0, 
                 sptr_t lineNumber = 1 );

    static FileDetails fromString( const QString &str );

    FileDetails &operator=( const FileDetails &other );

but that can get you into a world of hurt.

FileDetails::FileDetails() :
    m_lexerName( "null" )
    , m_eolMode( SC_EOL_LF )
    , m_pos( 0 )
    , m_lineNumber( 0 )
    , m_lineWrap( SC_WRAP_NONE )
    , m_readOnly( false )
    , m_isDirty( false )
    , m_autoIndentEnabled( false )
{
    if ( Overlord::getInstance() != nullptr )
    {
        m_eolMode = Overlord::getInstance()->platformEOLType();
        m_autoIndentEnabled = Overlord::getInstance()->autoIndentEnabled();
    }
}

FileDetails::FileDetails( const FileDetails &other ) :
    m_fileName( other.m_fileName )
    , m_cacheFileName( other.m_cacheFileName )
    , m_lexerName( other.m_lexerName )
    , m_tabText( other.m_tabText )
    , m_eolMode( other.m_eolMode )
    , m_pos( other.m_pos )
    , m_lineNumber( other.m_lineNumber )
    , m_lineWrap( other.m_lineWrap )
    , m_readOnly( other.m_readOnly )
    , m_isDirty( other.m_isDirty )
    , m_autoIndentEnabled( other.m_autoIndentEnabled )
{}

FileDetails::FileDetails( const QString fileName ) :
    m_fileName( fileName )
    , m_pos( 0 )
    , m_lineNumber( 0 )
    , m_lineWrap( SC_WRAP_NONE  )
    , m_readOnly( false )
    , m_isDirty( false )
{
    m_lexerName = Overlord::getInstance()->getLexerNameForFile( fileName );
    m_autoIndentEnabled = Overlord::getInstance()->autoIndentEnabled();
    m_eolMode = Overlord::getInstance()->platformEOLType();
}

FileDetails::FileDetails( const QString fileName, const QString cacheFileName, const QString lexerName,
                          const QString tabText, int eolMode, sptr_t pos ) :
    m_fileName( fileName )
    , m_cacheFileName( cacheFileName )
    , m_lexerName( lexerName )
    , m_tabText( tabText )
    , m_eolMode( eolMode )
    , m_pos( pos )
    , m_lineNumber( 0 )
    , m_lineWrap( SC_WRAP_NONE )
    , m_readOnly( false )
    , m_isDirty( false )
{
    m_autoIndentEnabled = Overlord::getInstance()->autoIndentEnabled();
}

FileDetails::FileDetails( const QString fileName, const QString cacheFileName, const QString lexerName,
                          const QString tabText, int eolMode, sptr_t pos, sptr_t lineNumber ) :
    m_fileName( fileName )
    , m_cacheFileName( cacheFileName )
    , m_lexerName( lexerName )
    , m_tabText( tabText )
    , m_eolMode( eolMode )
    , m_pos( pos )
    , m_lineNumber( lineNumber )
    , m_lineWrap( SC_WRAP_NONE )
    , m_readOnly( false )
    , m_isDirty( false )
{
    m_autoIndentEnabled = Overlord::getInstance()->autoIndentEnabled();
}

You see, many of the default values are controlled by user configuration. If we borrow from our std::optional example and plunge headlong into C++17 we can clean this up quite a bit.

C++17 Version

class FileDetails
{
public:

    FileDetails( std::optional<const QString> fileName      = std::nullopt,
                 std::optional<const QString> cacheFileName = std::nullopt,
                 std::optional<const QString> lexerName     = std::nullopt,
                 std::optional<const QString> tabText       = std::nullopt,
                 std::optional<int>           eolMode       = std::nullopt,
                 std::optional<sptr_t>        pos           = std::nullopt,
                 std::optional<sptr_t>        lineNumber    = std::nullopt,
                 std::optional<int>           lineWrap      = std::nullopt,
                 std::optional<bool>          readOnly      = std::nullopt,
                 std::optional<bool>          autoIndent    = std::nullopt,
                 std::optional<bool>          isDirty       = std::nullopt );

    FileDetails( const FileDetails &other ) = default;

...

private:
    // QString initializes itself to empty string
    // only have to set the one we need filled in
    //
    QString m_fileName;
    QString m_cacheFileName;
    QString m_lexerName {"null"};
    QString m_tabText;
    int     m_eolMode {SC_EOL_LF};
    sptr_t  m_pos {0};
    sptr_t  m_lineNumber {0};
    int     m_lineWrap {SC_WRAP_NONE};
    bool    m_readOnly {false};
    bool    m_isDirty  {false};
    bool    m_autoIndentEnabled {false};
};

The = default on the copy constructor is actually part of C++11 it just didn’t work well early on with third party libraries so nobody used it. This definitely won’t work with more complex classes in Qt, CopperSpice, etc. which deliberately disable copy constructors. Has to do with dynamic memory allocation and reference counting and other yadda yadda.

We now have only one constructor. Every parameter is optional. I even added some that weren’t on the other constructors. Some of you won’t like the following coding style, I don’t care. I use the Barr Group coding standard for medical device work and you never have a naked if statement.

FileDetails::FileDetails(
    std::optional<const QString> fileName,
    std::optional<const QString> cacheFileName,
    std::optional<const QString> lexerName,
    std::optional<const QString> tabText,
    std::optional<int>           eolMode,
    std::optional<sptr_t>        pos,
    std::optional<sptr_t>        lineNumber,
    std::optional<int>           lineWrap,
    std::optional<bool>          readOnly,
    std::optional<bool>          autoIndent,
    std::optional<bool>          isDirty )
{
    if ( fileName.has_value() )
    {
        m_fileName = fileName.value();
    }

    if ( cacheFileName.has_value() )
    {
        m_cacheFileName = cacheFileName.value();
    }

    if ( lexerName.has_value() )
    {
        m_lexerName = lexerName.value();
    }
    else
    {
        // a default construction will not have a file name so we
        // only override default value if file name provided.
        if ( m_fileName.length() > 0 )
        {
            m_lexerName = Overlord::getInstance()->getLexerNameForFile( m_fileName );
        }
    }

    if ( tabText.has_value() )
    {
        m_tabText = tabText.value();
    }

    if ( eolMode.has_value() )
    {
        m_eolMode = eolMode.value();
    }
    else
    {
        m_eolMode = Overlord::getInstance()->platformEOLType();
    }

    if ( pos.has_value() )
    {
        m_pos = pos.value();
    }

    if ( lineNumber.has_value() )
    {
        m_lineNumber = lineNumber.value();
    }

    if ( lineWrap.has_value() )
    {
        m_lineWrap = lineWrap.value();
    }

    if ( readOnly.has_value() )
    {
        m_readOnly = readOnly.value();
    }

    if ( autoIndent.has_value() )
    {
        m_autoIndentEnabled = autoIndent.value();
    }
    else
    {
        m_autoIndentEnabled = Overlord::getInstance()->autoIndentEnabled();
    }

    if ( isDirty.has_value() )
    {
        m_isDirty = isDirty.value();
    }


}

Previously we had to have multiple constructors which is a maintenance time bomb. I’ve had to deal with that at client sites. Classes with six or more constructors because they had been using C++98 or C++11 standard and now you need to add a field. Always one that gets missed. Leads to very difficult to track down bugs.

This is much more maintainable. Anything that could have a hard coded default value has it assigned in the header file. Use of std::optional allows us to fill in as many parameters as we wish. Default values coming from the user configuration are pulled in as needed.

Roland Hughes started his IT career in the early 1980s. He quickly became a consultant and president of Logikal Solutions, a software consulting firm specializing in OpenVMS application and C++/Qt touchscreen/embedded Linux development. Early in his career he became involved in what is now called cross platform development. Given the dearth of useful books on the subject he ventured into the world of professional author in 1995 writing the first of the "Zinc It!" book series for John Gordon Burke Publisher, Inc.

A decade later he released a massive (nearly 800 pages) tome "The Minimum You Need to Know to Be an OpenVMS Application Developer" which tried to encapsulate the essential skills gained over what was nearly a 20 year career at that point. From there "The Minimum You Need to Know" book series was born.

Three years later he wrote his first novel "Infinite Exposure" which got much notice from people involved in the banking and financial security worlds. Some of the attacks predicted in that book have since come to pass. While it was not originally intended to be a trilogy, it became the first book of "The Earth That Was" trilogy:
Infinite Exposure
Lesedi - The Greatest Lie Ever Told
John Smith - Last Known Survivor of the Microsoft Wars

When he is not consulting Roland Hughes posts about technology and sometimes politics on his blog. He also has regularly scheduled Sunday posts appearing on the Interesting Authors blog.