Search code examples
c++classg++llvm-clanginitializer-list

Initializer list seems to be dependent on the order of variables in a class


I am new at C++ and this is my first post here. I am trying to understand initializer lists. The problem is that I get more and more confused. I have made a simple program, that is of no use, but I get a warning I don't understand.

#include <iostream>

using namespace std;

class Base{

    private:
        string monkey2 = "";
        string monkey1 = "";
    public:
        Base(string) : monkey2{monkey1}{
            cout << "monkey1 " << monkey1 << endl;
            cout << "monkey2 " << monkey2 << endl;
        }
};

int main()
{
    return 0;
}

The warning I get is:

field 'monkey' is uninitialized when used here

and an arrow pointing on the row directly below public:.

The strange thing is if I change the order of the strings from:

private:
    string monkey2 = "";
    string monkey1 = "";
public:

To:

private:
    string monkey1 = "";
    string monkey2 = "";
public:

The warning disappears and I don't understand why. I can compile it using g++ but when using my editor vim with YCM and Clang 7.0.0 i get a warning. I have tried to activate as much warnings as i can in g++, but I can not get the same warning. Is my code or. or is it Clang?


Solution

  • Data member are initialized in the order they are declared. Hence, when you have

    class Base {
        public:
            Base();
    
        private:
            string monkey2 = "";
            string monkey1 = "";
    };
    

    the variable monkey2 will be initialized before monkey1, irrespective of what the ctor Base::Base() does. This can lead to hard to spot issues, when the initialization of data members depend on each other. Here,

    Base() : monkey2{monkey1} { /* ... */ }
    

    monkey2 is initialized by monkey1, but due to the order of declaration, monkey1 isn't initialized at this point as monkey2 gets initialized first. That's why you get the warning, and that's also why the warning is mitigated by declaring monkey1 first - then, monkey1 is set to "" via the in-class initializer and monkey2 is initialized with the already initialized monkey1.

    The following (admittedly subjective) guidelines apply to such a scenario:

    • If you can avoid initialization dependencies across data members, do so.
    • If you have a ctor initializer list, strictly follow the order in which data members are declared (see C.47).
    • If there is a sensible default value for a data member, possibly across ctor overloads, use in-class initialization (but then, don't add another initialization instruction as in your Base::Base(string) implementation (see C.48).