1

I have code:

struct testInit {
    testInit(int i1_) 
        : i1(i1_) {
        std::cout << "testInit::testInit" << std::endl;
    }

    int initI2() {
        std::cout << "testInit::initI2::i1: " << i1 << std::endl;
        return i1;
    }

    const int i1 = 1;
    const int i2 = initI2();

};

int main() {
    testInit ti(3);

    std::cout << "i1: " << ti.i1 << std::endl;
    std::cout << "i2: " << ti.i2 << std::endl;

    return 0;
}

The output is:

testInit::initI2::i1: 3
testInit::testInit
i1: 3
i2: 3

So, I am wondering what exactly is order of initialization of class members. I thought that the output should be i1: 3 and i2: 1 - which is obviously wrong - but why?

Tanveer Badar
  • 5,438
  • 2
  • 27
  • 32
  • Variables are always initialized in their declaration order, so `i1` then `i2` in your code, no matter how you initialize them. Your constructor is equivalent to `testInit(int _i1) : i1(_i1), i2(initI2()) { }`. – Holt Apr 23 '18 at 07:49
  • "I thought that the output should be..." why? – n. m. could be an AI Apr 23 '18 at 07:51

3 Answers3

4

I am wondering what exactly is order of initialization of class members

Members are always initialized in order of declaration in the class definition, so i1 will be initialized at first, then i2.

3) Then, non-static data members are initialized in order of declaration in the class definition.

And for testInit::testInit(int), both member initializer list and default member initializer are specified on i1; the default member initializer will be ignored.

If a member has a default member initializer and also appears in the member initialization list in a constructor, the default member initializer is ignored.

Then for testInit ti(3);, i1 is initialized with 3 via member initializer list firstly, then i2 is initialized with initI2() via default member initializer, then it'll be 3 too.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • quite amazing you've answered the same question about default and mem-initializer with const. – Joseph D. Apr 23 '18 at 08:01
  • @codekaizer They're not same; I think the point of this question is the initialization order (and the mix of member initializer list and default member initializer makes it complex), isn't it? – songyuanyao Apr 23 '18 at 08:03
  • true. i mean some elements are the same. but the real question is about the initialization order. you've enlightened me with this one. – Joseph D. Apr 23 '18 at 08:07
  • thanks for explanation! – malebranchi Apr 23 '18 at 13:22
2

From dcl.init.list#4

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions, are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.

which for your case is:

testInit ti(3); // replaces the default initialization of i1 using the mem-initializer 

then i2 is default initialized to 3 using initI2() for i1 is already 3 by this time (i.e. testInit is already well-defined).

Joseph D.
  • 11,804
  • 3
  • 34
  • 67
-1

Instead of citing the standard - which is already done by another answer - I'm giving you some empirical data:

#include <iostream>

struct testInit {
    testInit(int i1_) 
        : i1(i1_) {
        std::cout << "testInit::testInit" << std::endl;
    }

    int initI2() {
        std::cout << "testInit::initI2::i1: " << i1 << std::endl;
        return i1;
    }

    const int i2 = initI2();
    const int i1 = 1;
};

int main() {
    testInit ti(3);

    std::cout << "i1: " << ti.i1 << std::endl;
    std::cout << "i2: " << ti.i2 << std::endl;

    return 0;
}

Output:

testInit::initI2::i1: 0
testInit::testInit
i1: 3
i2: 0

Experimenting around like this is of course by no means "enough" to be sure about some behaviour of C++, simply because you can easily run into undefined behaviour (which the above code actually has) or implementation defined behaviour.

But nonetheless: Just experiment a bit. It won't hurt and you're probably going to remember far better what you've "discovered" vs what you've read somewhere.

undefined behaviour due to the access to the uninitialized i1)

Community
  • 1
  • 1
Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • maybe it will be useful why the output is like that. – Joseph D. Apr 23 '18 at 08:06
  • @codekaizer that's not the point of my answer. It's answered clearly in the (now available) other answers, after all. My answer hints that experimenting a bit would've given the answer faster than asking here ... and would probably be remembered better than after reading a passage from the standard. – Daniel Jour Apr 23 '18 at 08:16
  • 1
    What if the order of initialization was *implementation defined*? This would give you the behaviour on just one platform/compiler. Consider the same question about the [order of destruction of function parameters](https://stackoverflow.com/questions/36992039/what-is-the-order-of-destruction-of-function-arguments). – Zereges Apr 23 '18 at 08:24
  • I wouldn't suggest experimenting with C++ is enough. It's tremendously unfortunate, but there are enough UB/unspecified/implementation-defined stuff that wrecks you. – Passer By Apr 23 '18 at 08:38
  • @passerby of course I do **not** mean that one should stop after experimenting nor that it's "enough". It's just better to learn stuff while doing vs just reading about it. Edited my answer to state this clearly. – Daniel Jour Apr 23 '18 at 14:16