16

I was using somebody else's class which was acting odd when I pushed it into a vector. It involves a member variable which is a reference to another member variable. Here is the smallest self-contained example:

#include <iostream>
#include <vector>

class Myclass {
public: 
  Myclass() : a(1.0) {}

  float a;
  float &a_ref = a;

  void addOne() {
    a = a + 1.0;
  }
};

int main() {
  Myclass instance1;
  instance1.addOne();

  //prints 2:
  std::cout << "instance.a_ref is " << instance1.a_ref << std::endl;

  std::vector<Myclass> vec;
  Myclass instance2;
  vec.push_back(instance2);

  vec.at(0).addOne();

  //prints 1;
  std::cout << "vec.at(0).a_ref is " << vec.at(0).a_ref << std::endl;
  return 0;
}

I was compiling with g++ and -std=c++11, so I didn't notice the problem for a while. I see now the issue is probably to do with the synthesised copy constructor and the reference member. But what I'm not sure about is:

  1. Why is there different behaviour when the object is in a vector ?
  2. Why does g++ not give any warnings about this, using c++11 standard ?

Bonus question because I'm curious:

  1. What is initialized first, a or a_ref?
badger5000
  • 650
  • 7
  • 17
  • 2
    C++ standard stated that nonstatic data members shall be initialized in the order they were declared in the class definition. – Alper Jan 23 '15 at 11:46
  • thanks Alper - just to confirm, does that still apply if they are initialized in different ways (one in initializer list, one in-class) – badger5000 Jan 23 '15 at 11:48
  • 1
    Initialize list before the constructor body. Note that as Alper said the initialize list is not done in the order you entered but in the order of the declarations. – Richard Critten Jan 23 '15 at 11:51
  • 1
    The order of member initializers in the list is irrelevant: the actual order of initialization can be found [here](http://en.cppreference.com/w/cpp/language/initializer_list). – Alper Jan 23 '15 at 11:52

2 Answers2

16

The problem is indeed with the defaulted copy constructor. The defaulted copy constructor initialises all members from the members of the source object. That is, the defaulted copy constructor is identical to this:

Myclass(const Myclass &src) :
  a(src.a),
  a_ref(src.a_ref)
{}

The defaulted copy constructor initialises all members, so it ignores any in-class initialisers.

This is also why pushing into a vector causes the problem. vec.at(0) was created as a copy of instance2, which means vec.at(0).a_ref refers to instance2.a. You could easily verify this by printing their addresses (live example).

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
7

The implicitly defined copy/move constructor:

[...] performs a performs a memberwise copy/move of its bases and members. [ Note: brace-or-equal-initializers of non-static data members are ignored. [...]

In particular, reference members are direct-initialized to refer to the same object the corresponding reference member in the source object refers to.

So in your case, vec.at(0).a_ref refers to the member a of instance2.

This is not detected by the compiler because in general reference members are expected to refer to a longer-lived object outside the class.

ecatmur
  • 152,476
  • 27
  • 293
  • 366