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

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

本文关键字:back push 成员 引用类型 变量 向量 C++11      更新时间:2023-10-16

我正在使用别人的类,当我把它推到向量中时,它表现得很奇怪。它涉及一个成员变量,该成员变量是对另一个成员变量的引用。下面是最小的自包含示例:

#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. 为什么g++没有给出任何警告,使用 c++11 标准?

奖励问题,因为我很好奇:

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

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

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

默认的复制构造函数初始化所有成员,因此它会忽略任何类内初始化器。

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

隐式定义的复制/移动构造函数:

[...]执行其基和成员的成员复制/移动。[ 注意:忽略非静态数据成员的大括号或等于初始值设定项。[...]

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

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

编译器不会检测到这一点,因为在一般情况下,引用成员应引用类外部生存期较长的对象。