-1

I am trying to understand how classes are compiled in C++. And so creating different examples to understand initialization rules (along with theory from books). My example code is as follows:

#include <iostream>
using namespace std;

class NAME {
public:
    NAME(int p) : x(r) {
        cout << x << endl;
    }
    int &x;
    int r=3;  
};

int main() {
    NAME obj(4);
    return 0;
}

My question is that does the order in which I have specified the data members &x and int r=3 matter? I have checked by reversing the order of those two and still the program works. I have read that compilation of classes happens in two steps:

  1. All member declarations are compiled.
  2. Then function bodies are complied.

Now my question is according to this rule the declarations of reference type x should happened first (since compilation happens in order of variable declaration/definition) and then int type r should happen. And then constructor bodies should be compiled. But since we must always initialize a reference type, how is this program working when we have not initialized x? Also, I know that constructor initializer list are used to initialize x from r. I think I am getting confused about the fact that when using the initializer list, will the in-class declaration happen first or will the constructor list directly initialize the members? If it is the latter then why does the order of arguments in the initializer list matter?

PS: I know we have initialized x through the initializer list x(r) but what happens to the in-class definitions/declarations of x and r? Will they be ignored when a initializer list is present? And in what order does all this happens?

lopho
  • 166
  • 10
Anoop Rana
  • 19,715
  • 4
  • 12
  • 33
  • 1
    *"how is this program working when we have not intialize x"*, you initialize it with `x(r)`... – Jarod42 Feb 19 '21 at 10:52
  • 2
    Does this answer your question? [Order of member constructor and destructor calls](https://stackoverflow.com/questions/2254263/order-of-member-constructor-and-destructor-calls) – underscore_d Feb 19 '21 at 10:52
  • @Jarod42 yeah i know we have initialized x through the initializer list x(r) but what happens to the in-class definitions/declarations of x and r? Will they be ignored when a initializer list is present? And in what order does all this happens? – Anoop Rana Feb 19 '21 at 10:57
  • don't confuse declaration with initialization. you only declare `x` in the class body, and initialize it with the reference to `r` in the initializer list. – lopho Feb 19 '21 at 11:38
  • @lopho I have no confusion between declaration and initialization. That is why the question exist. We cannot only declare a reference variable. We must always initialize it. But here in the code we first declare it and initialize it using the initializer list. How is this possible? – Anoop Rana Feb 19 '21 at 11:54
  • take a look at this https://en.cppreference.com/w/cpp/language/data_members#Member_initialization section 2) – lopho Feb 19 '21 at 14:40

1 Answers1

1

The example code is valid, though the pattern is a bit dangerous, and similar code could have Undefined Behavior.

It's not exactly accurate that class member declarations then definitions are compiled, or at least not in the sense you're interpreting it. It's true that function bodies and object member initializers inside a class declaration can use names of other members of the same class, even if they were not yet declared. But this rule only affects name lookup, and none of the rules about object lifetime or initialization. Exactly how and when a compiler does all this, we don't usually need to worry about for understanding what code will do.

Related terminology corrections to your one paragraph:

the declarations initialization of reference type x should happened first (since compilation initialization happens in order of variable declaration/definition) and then int type r should happen. And then constructor bodies should be compiled executed.

This does lead to situations, as in your example, where a member could be used before it is initialized, so the member's "lifetime" has not yet started. In the example, obj.x is initialized before obj.r. This situation needs to be handled with care, since most operations on an object outside its lifetime are undefined behavior. (For details, see Standard paragraph [basic.life]/7.) But just binding a reference to such an object is permitted. Of course, then most operations using that reference are also undefined behavior until the referenced object actually is initialized.

when we use initializer list then will the in-class declaration will happen first or the constructor list will directly initialize the members?

Whenever object initialization uses a constructor with a mem-initializer-list which specifies how to initialize a given member, any in-class initializer on that same member is entirely ignored. So the =3 on the declaration of member r does nothing in your example. It would still be possible for other constructors to omit initialization of r, and they would use the =3. If you added a default constructor NAME() = default;, this would also use it, since the default constructor's defaulted definition is like not using a mem-initializer-list at all.

aschepler
  • 68,873
  • 9
  • 101
  • 151
  • It's OK to bind a reference to an object before that object's lifetime starts (as you point out) so I don't feel paranoia is justified here , unless OP is starting to do other funky stuff in the member initializer list, which is equally well a problem whether or not there are references involved. In fact the OP code is not UB but would be if `x` were not a reference! – M.M Feb 19 '21 at 13:38
  • @M.M Sure - "a bit dangerous", not "a terrible idea". – aschepler Feb 19 '21 at 13:43
  • @aschepler I read the following in a C++11 book: "The compiler processes classes in two steps:the member declarations are compiled first after which the member function bodies if any are processed". Is the above quoted text incorrect? As an example, consider two private members defined in the following order: `int x=r ; int r=7` . Is this undefined behavior ? If yes then i executed this and printed the value of x and it seems x has value 0 which confirms that x is undefined.My question is how `x` is initialized (in this case) with `r` before `r` has been initialized?How(order) does this work? – Anoop Rana Feb 19 '21 at 14:17
  • @JasonLiam It's a decent way to think about the point that there are two sorts of "scopes" in class definitions. But an actual compiler could have 10 steps, or essentially one step with member names "fixed up later". Unless we're working with the compiler's source code, that doesn't really matter as long as it ends up respecting the rules for these two member name "scopes". – aschepler Feb 19 '21 at 14:23
  • @JasonLiam Yes, that example is undefined behavior, if any object actually uses the `=r` initializer for member `x` (no matter how `r` is initialized). The `=r` initializer does name member `r` even though it comes before the declaration of `r` in the source code. Member `x` must always be initialized before member `r` because of the declaration order. The attempt to copy an `int` value from `r` to initialize `x` counts as an "access" of object `r` before the lifetime of `r` begins, violating [\[basic.life\]/(7.1)](https://timsong-cpp.github.io/cppwp/n4861/basic.life#7). – aschepler Feb 19 '21 at 14:26