为什么空基类也是成员变量时禁止空基优化?

ima*_*ett 14 c++ inheritance class memory-layout language-lawyer

空基优化很棒。但是,它具有以下限制:

如果空基类之一也是第一个非静态数据成员的类型或类型的基类,则禁止空基类优化,因为相同类型的两个基子对象在对象表示中需要具有不同的地址派生最多的类型。

要解释此限制,请考虑以下代码。该static_assert会失败。而更改FooBar改为继承自Base2将避免错误:

#include <cstddef>

struct Base  {};
struct Base2 {};

struct Foo : Base {};

struct Bar : Base {
    Foo foo;
};

static_assert(offsetof(Bar,foo)==0,"Error!");
Run Code Online (Sandbox Code Playgroud)

我完全理解这种行为。我明白的是为什么存在这种特殊行为。很明显,添加它是有原因的,因为它是一个明确的添加,而不是一个疏忽。这样做的理由是什么?

特别是,为什么要要求两个基础子对象具有不同的地址?在上面,Bar是一个类型并且foo是该类型的成员变量。我不明白为什么基类对Bar类型的基类很重要,foo反之亦然。

事实上,如果有的话,我希望它&fooBar包含它的实例的地址相同——因为它需要在其他情况下(1)。毕竟,我对virtual继承并没有做任何花哨的事情,无论如何基类都是空的,并且编译Base2显示在这种特殊情况下没有任何问题。

但显然这种推理在某种程度上是不正确的,并且在其他情况下需要这种限制。

假设答案应该是针对 C++11 或更新版本的(我目前使用的是 C++17)。

(1)注意:EBO 在 C++11 中得到了升级,特别是对于StandardLayoutTypes 来说是强制性的(尽管Bar上面的 不是StandardLayoutType)。

n31*_*159 4

好吧,似乎我一直都错了,因为对于我的所有示例,基础对象都需要存在一个 vtable,这将阻止空基础优化开始。我会让这些例子成立,因为我认为它们给出了一些有趣的例子,说明为什么拥有唯一地址通常是一件好事。

更深入地研究了整个问题后,当第一个成员与空基类具有相同类型时,没有技术原因禁用空基类优化。这只是当前 C++ 对象模型的一个属性。

但对于 C++20,将有一个新属性[[no_unique_address]]告诉编译器非静态数据成员可能不需要唯一地址(从技术上讲,它可能会重叠 [intro.object]/7)。

这意味着(强调我的)

非静态数据成员可以共享另一个非静态数据成员的地址或基类的地址,[...]

因此,可以通过为第一个数据成员提供属性来“重新激活”空基类优化[[no_unique_address]]我在这里添加了一个示例,展示了这种情况(以及我能想到的所有其他情况)的工作原理。

错误示例问题通过此

由于看起来空类可能没有虚方法,所以让我添加第三个示例:

int stupid_method(Base *b) {
  if( dynamic_cast<Foo*>(b) ) return 0;
  if( dynamic_cast<Bar*>(b) ) return 1;
  return 2;
}

Bar b;
stupid_method(&b);  // Would expect 0
stupid_method(&b.foo); //Would expect 1
Run Code Online (Sandbox Code Playgroud)

但最后两次调用是相同的。

旧的例子(可能不会回答这个问题,因为空类可能不包含虚拟方法,看起来)

在上面的代码中考虑以下示例(添加了虚拟析构函数)

void delBase(Base *b) {
    delete b;
}

Bar *b = new Bar;
delBase(b); // One would expect this to be absolutely fine.
delBase(&b->foo); // Whoaa, we shouldn't delete a member variable.
Run Code Online (Sandbox Code Playgroud)

但是编译器应该如何区分这两种情况呢?

也许不那么做作:

struct Base { 
  virtual void hi() { std::cout << "Hello\n";}
};

struct Foo : Base {
  void hi() override { std::cout << "Guten Tag\n";}
};

struct Bar : Base {
    Foo foo;
};

Bar b;
b.hi() // Hello
b.foo.hi() // Guten Tag
Base *a = &b;
Base *z = &b.foo;
a->hi() // Hello
z->hi() // Guten Tag
Run Code Online (Sandbox Code Playgroud)

但如果我们有空基类优化,最后两个是相同的!

  • 当它不是多态时,你不能使用“dynamic_cast”(有一些与这里无关的小例外)。 (2认同)