有关代码版本中的 vtable 和查找的问题

问题描述 投票:0回答:1

我正在使用物理实验的协作代码库。一般用例,比如粒子,是这样的:

// Code_Version_v1:
class Particle {
   public:
   virtual float get_momentum() { return NAN; }
}

class Particle_v1 : public Particle {
  private:
  momentum;
  public:
  override float get_momentum() { return momentum; }
}

然后实验收集了大量的粒子数据,这些数据存储在数据库中(使用CERN的ROOT TTrees,这完全是另一个故事),客户端代码通常有这样的内容:

   Particle* part = ttree->get_particle();
   std::cout << part->get_momentum() << std::endl;

其中 ttree 对象返回某个 Particle 继承类的实例。

如果代码更新到Code_Version_v2,并在基类Particle中添加了新方法,但随后使用之前代码版本中的Particle_v1,是否会导致某些vtable方法调用错误?

例如:

// Code_Version_v2 library
class Particle { // update with a new method
   public:
   virtual float get_momentum() { return NAN; }
   virtual float get_mass() { return NAN; }
}

// class Particle_v1 is unchanged

class Particle_v2 : public Particle {
  private:
  float momentum;
  float mass;
  
  public:
  override float get_momentum() { return momentum; }
  override float get_mass()     { return mass; }
}

然后代码会执行以下操作:

// Using Code_Version_v2 library
Particle* part = ttree->get_particle(0); // returns Particle_v1* generated with Code_Version_v1
std::cout << part->get_momentum() << std::endl; // now run with libraries from Code_Version_v2

C++ 标准是否保证编译器等...将足够智能以正确实现 vtable 查找?我意识到这可能取决于 ROOT 实现如何保存和流式传输 TTree 中的对象。我有一些微妙的错误,随着添加

cout
语句而消失,所以它似乎是某种内存损坏。所有见解均受到赞赏。谢谢!

c++ root vtable
1个回答
1
投票

是的,当基类和派生类跨库版本扩展时,特别是在涉及 vtable 时,C++ 中可能会出现 ABI 兼容性问题。在基类中添加新的虚拟方法将重新布局 vtable,可能会破坏二进制兼容性。

在您的示例中,如果使用

Particle_v1
创建了
Code_Version_v1
对象,并且稍后链接到
Code_Version_v2
,则调用
get_mass()
将是未定义的行为,因为 vtable 布局已更改,但
Particle_v1
对象仍遵循旧布局。

使用添加的

cout
语句进行调试可以修改内存布局或访问时序,从而可能掩盖问题(“Heisenbug”)。

最佳实践:

  1. 保持 ABI 稳定性:如果扩展类,请创建全新的派生类而不修改现有类。
  2. 对共享库进行版本控制,使用命名空间或其他形式的唯一标识。
  3. 如果您预计多态类型的结构会发生变化,请避免以序列化形式存储多态类型。相反,请考虑保存最少的数据并重建对象。

ROOT 的 TTree 序列化可能会使情况进一步复杂化,特别是如果它不考虑 ABI 更改的话。因此,在升级与多态对象的序列化形式互操作的库时应格外小心。

© www.soinside.com 2019 - 2024. All rights reserved.