C++ class 实例化可以在运行时改变它的大小吗

Can a C++ class instantiation change its size during runtime

我这里有一个非常特殊的情况...我继承了一些旧的 C++ 代码(C++11 之前),它又长又复杂,其中一半是用 C 编写的,另一半是用 C 编写的- with-classes 心态(即:classes 有更多的数据成员,没有太多的 class 方法,直接操作数据成员......绝对这个项目需要一些重构,就是这样我现在正在工作)。但是代码暴露了一些我觉得费解的问题。

首先让我们采用以下非常简单(无错误)的情况:

#include <iostream>

static int c = 0;

struct Bar
{
    Bar() : base_id(++c) { std::cout << "Bar "<< base_id << std::endl;}
    int base_id;
};

struct Foo : public Bar
{
  Foo() : x(c) { std::cout << "Foo "<< x << std::endl;}
  int x;
};

int main()
{
  Bar* b = new Foo[200];
  Foo *p;

  for(p = (Foo*)b; p - (Foo*)b < 200; p ++ )
  {
      std::cout << p->base_id << " " << ((Foo*)p)->x 
         << " p-b=" << (unsigned)(p-(Foo*)b) << std::endl;
  }


  delete[] b;
}

或文本版本:我们通过 new Derived 创建一个基本 class 对象数组。到目前为止一切顺利,这就是大型复杂应用程序所做的(classes 更有层次,构造函数做的更多),但全局静态变量 (c) 也存在于大型应用程序中复杂的应用程序,它使用它作为唯一的对象标识符。然后大型复杂的应用程序开始工作,等等......

然后在某个时间点我们遍历对象数组,做一些工作。迭代看起来与我在这里写的完全一样,在 for 循环中找到,具有详尽的指针算法。我这里的示例只是在大型复杂应用程序中打印对象 ID (m) 更多的事情已经完成。

但是在大型复杂应用程序的某处发生了一些神奇的事情......在列表的一半之后的某个时间,对象(通过 p 指针获得)不再有效,它们的 base_ids 显示的数据对我来说非常像指针和其他数据成员的值。但是,如果我检查特定索引处的数组成员,则该值有效(即:正确增加对象 ID)。

我还检查了以下内容:

所以...我得出一个结论:

(问题来了:)

我不知道 C++ 对象可以在运行时更改其大小(如果可能,请与我们分享),所以除了我已经确定的可能问题之外,社区中的任何人都可能知道为什么指针算法的行为如此奇怪?

(是的,该项目将被转换为带有标准容器等的适当的 c++11 ......但现在我只对这个特定问题感兴趣)

你的问题在这里:

Bar* b = new Foo[200];

b 指向 Foo 数组第一个元素中的 Bar 子对象,但不能用作访问数组的方法;尺码不对。不断地将其转换回 Foo 指针似乎可行,但很容易失败。 (当 Bar 子对象与它所属的 Foo 对象不在同一地址时,面对多重继承情况会变得更糟。)

您发布的代码在进行指针运算之前总是小心地将 b 转换为 Foo*。 (甚至太小心了:((Foo*)p)->x 可能只是 p->x。)但是如果在任何时候,任何地方,有人忘记这样做(例如尝试 b[i],或 b+i,或(Foo*)(b+i)…),它将导致您描述的行为。 或者,随着所有这些向下转换的进行,有人将 Bar* 向下转换为 Baz*,其中 Baz 也继承自 Bar,但大小与 Foo 不同。 这也会以不稳定的方式覆盖字段。

此外,delete[] b is undefined behavior。因此,编译器有可能得出 b 确实指向 Bar 个实例,并省略或搞砸对 Foo* 的强制转换——如今编译器会做这种事情.

总而言之,Bar* b = new Foo[200];是行不通的。使用

Foo* fp = new Foo[200];

相反。如果出于某种原因,您需要一个 Bar*,您可以在后面加上

Bar* b = fp;

但不清楚你为什么需要这个;您可以在需要 Bar* 时使用 fp