С++ 11 переменная-член ссылочного типа, различное поведение после vector push_back

Я использовал какой-то другой класс, который работал нечетно, когда я ввел его в вектор. Он включает переменную-член, которая является ссылкой на другую переменную-член. Вот самый маленький самодостаточный пример:

#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;
}

Я компилировал с g++ и -std=c++11, поэтому я некоторое время не замечал проблему. Теперь я вижу, что проблема связана, вероятно, с синтезированным конструктором копии и ссылочным элементом. Но я не уверен в следующем:

  • Почему существует другое поведение, когда объект находится в векторе?
  • Почему g++ не дает никаких предупреждений об этом, используя стандарт С++ 11?

Бонусный вопрос, потому что мне любопытно:

  1. Что инициализируется сначала, a или a_ref?

Ответы

Ответ 1

Проблема действительно связана с конструктором копии по умолчанию. По умолчанию конструктор копирования инициализирует всех членов из элементов исходного объекта. То есть конструктор копии по умолчанию идентичен этому:

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

По умолчанию конструктор копирования инициализирует все члены, поэтому он игнорирует любые инициализаторы в классе.

Это также приводит к тому, что нажатие на вектор вызывает проблему. vec.at(0) был создан как копия instance2, что означает, что vec.at(0).a_ref относится к instance2.a. Вы можете легко проверить это, распечатав свои адреса (живой пример).

Ответ 2

Неявно определенный конструктор копирования/перемещения:

[...] выполняет a выполняет поэтапную копию/перемещение своих оснований и членов. [Примечание: скопированные или равные инициализаторы нестатических членов данных игнорируются. [...]

В частности, ссылочные элементы инициализируются напрямую, чтобы ссылаться на один и тот же объект, к которому относится соответствующий ссылочный элемент в исходном объекте.

Итак, в вашем случае vec.at(0).a_ref относится к элементу a instance2.

Это не обнаружено компилятором, потому что, как ожидается, общие члены-члены ссылаются на объект с более длительным сроком жизни вне класса.