引用类型的C++11成员变量,向量push_back后的不同行为

C++11 member variable of reference type, different behaviour after vector push_back

我使用的是别人的 class,当我将它推入向量时,它表现得很奇怪。它涉及一个成员变量,该成员变量是对另一个成员变量的引用。这是最小的独立示例:

#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编译的,所以一时没注意到这个问题。我现在看到问题可能与合成复制构造函数和引用成员有关。但我不确定的是:

  1. 为什么对象在向量中时会有不同的行为?
  2. 使用 c++11 标准,为什么 g++ 不对此发出任何警告?

奖金问题因为我很好奇:

  1. 先初始化什么,a还是a_ref

问题确实出在默认的复制构造函数上。默认的复制构造函数从源对象的成员中初始化所有成员。也就是说,默认的复制构造函数与此相同:

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

默认的复制构造函数初始化所有成员,因此它忽略任何 in-class 初始化程序。

这也是推入矢量导致问题的原因。 vec.at(0) 是作为 instance2 的副本创建的,这意味着 vec.at(0).a_ref 引用 instance2.a。您可以通过打印他们的地址 (live example) 轻松验证这一点。

隐式定义的copy/move构造函数:

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

特别是,引用成员被直接初始化为引用源对象中相应引用成员引用的同一对象。

所以在你的例子中,vec.at(0).a_ref 指的是 instance2 的成员 a

这不会被编译器检测到,因为通常引用成员应该引用 class 之外的长期对象。