4

I've recently come across this:

#include <iostream>
#include <vector> 
#include <string>
#include <algorithm>  //sort function
#include <functional> //functions utilities
#include <random>     //random numbers generation

using namespace std;
default_random_engine         generator;
uniform_int_distribution<int> distribution(0,9999);
auto randomer = bind(distribution, generator);


struct Test_struct {
    string  ord_as_string;
    int     ord;
    Test_struct() :
      ord(randomer()),
      ord_as_string(to_string(ord))
    {}
};

int main() {
  vector<Test_struct> my_vector;
  for(int i = 0; i < 100;  ++i) my_vector.push_back(Test_struct());
  for(auto& e: my_vector)
    cout << e.ord_as_string << " -> " << e.ord << endl;
}

Which prints the following sequence:

142 -> 0
0 -> 1315
1315 -> 7556
7556 -> 4586
4586 -> 5327
5327 -> 2189
2189 -> 470
470 -> 6788
6788 -> 6792
...

It doesn't make sense since the struct int and string attributes should refer to the same logical number. So I just changed the declaration to:

 struct Test_struct {
    int     ord;
    string  ord_as_string;
    Test_struct() :
      ord(randomer()),
      ord_as_string(to_string(ord))
    {}
};

I thought that it has something to deal with the order of declaration of the struct attributes and it worked:

0 -> 0
1315 -> 1315
7556 -> 7556
4586 -> 4586
5327 -> 5327
2189 -> 2189
470 -> 470
6788 -> 6788
...

So, can someone explain to me why does the reference to ord is kept in the following structs initializations when it is declared after the other attribute but initialized before?

Edit: Ok, members are initialized in the declaration order as said in the comments, then, why was it designed like that? It makes no sense for me.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Netwave
  • 40,134
  • 6
  • 50
  • 93
  • 2
    members are initialized in the order they are declared, irrespectively of the order you place them in the initializer list. Thats just how to rule is – 463035818_is_not_an_ai May 28 '18 at 14:49
  • @user463035818, ok, then can you explain to me why it was decided to be so? just being curious about this, because it makes no sense for me. – Netwave May 28 '18 at 14:50
  • 1
    not a dupe but related: https://stackoverflow.com/questions/1828037/whats-the-point-of-g-wreorder – 463035818_is_not_an_ai May 28 '18 at 14:51
  • from the top of my head i wouldnt know a better alternative. The way it is, the order of initialization is always the same, no matter which constructor is invoked, and with the appropriate level of warnings you cant miss a mistake... what would make sense for you? – 463035818_is_not_an_ai May 28 '18 at 14:54
  • i guess it has something to do with the c heritage. c doesnt have constructors but still needs to define an order of initialization of members – 463035818_is_not_an_ai May 28 '18 at 14:55
  • for me makes sense that if im calling the initialization of ord first, even if it was defined after ord_string it should be initialized before. I understand that it has to deal with some limitations, probably about c heritage, but i would like to understand it properly. Thanks @user463035818 :) – Netwave May 28 '18 at 14:57

1 Answers1

8

You are running into undefined behavior. The order of initialization of the members must match the order of declaration.

Since it does not, the behavior you are observing stems from the ord member being uninitialized and coincidentally reusing the same location in memory on every iteration of the loop. This location happens to hold the value you assigned in the iteration before.

You can read up here what the order of initialization is, and why it is that way. To summarize both the link and comments: the destructor has to destroy objects in the reverse order of initialization, and since there can be multiple constructors, there has to be a single source of truth, which is the order of declaration in the class.

midor
  • 5,487
  • 2
  • 23
  • 52
  • that is a big coincidence!, but, could you explain also why this was decided to behave like this? – Netwave May 28 '18 at 14:53
  • 8
    @Netwave: Because the order of destruction must be the *reverse* of the order of initialization, all of your constructors have to agree on the order of initialization. So there is one order: the order of declarations. Now, why C++ allows you to put them *out of order* without giving you a mandatory compile error? That's a different matter. – Nicol Bolas May 28 '18 at 14:55
  • That's exactly how it is. – midor May 28 '18 at 14:57