c++ - 当空基类也是成员变量时,为什么禁止空基优化?
问题描述
空基优化很棒。但是,它具有以下限制:
如果空基类中的一个也是第一个非静态数据成员的类型或类型的基类,则禁止空基优化,因为要求相同类型的两个基子对象在对象表示中具有不同的地址派生最多的类型。
要解释此限制,请考虑以下代码。将static_assert
失败。然而,更改其中之一Foo
或Bar
改为继承自Base2
将避免错误:
#include <cstddef>
struct Base {};
struct Base2 {};
struct Foo : Base {};
struct Bar : Base {
Foo foo;
};
static_assert(offsetof(Bar,foo)==0,"Error!");
我完全理解这种行为。我不明白为什么存在这种特殊行为。它显然是有原因的,因为它是明确的添加,而不是疏忽。这样做的理由是什么?
特别是,为什么要求两个基本子对象具有不同的地址?在上面,Bar
是一个类型并且foo
是该类型的一个成员变量。我不明白为什么基类Bar
对类型的基类很重要,foo
反之亦然。
事实上,如果有的话,我希望它与包含它&foo
的实例的地址相同——因为在其他情况下(1)需要它。毕竟,我没有对继承做任何花哨的事情,基类无论如何都是空的,并且编译表明在这种特殊情况下没有任何问题。Bar
virtual
Base2
但显然这种推理在某种程度上是不正确的,并且在其他情况下需要这种限制。
假设答案应该适用于 C++11 或更高版本(我目前使用的是 C++17)。
(1)注意:EBO 在 C++11 中得到了升级,特别是对于 s 来说是强制性StandardLayoutType
的(尽管Bar
上面的 s 不是 a StandardLayoutType
)。
解决方案
好的,似乎我一直都错了,因为对于我的所有示例,都需要存在一个用于基础对象的 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
但是最后两个电话是一样的。
老例子(可能不回答这个问题,因为空类可能不包含虚拟方法,似乎)
在上面的代码中考虑(添加了虚拟析构函数)以下示例
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.
但是编译器应该如何区分这两种情况呢?
而且可能不那么做作:
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
但是如果我们有空基类优化,最后两个是一样的!
推荐阅读
- c# - EF 核心异常 System.ArgumentException:Argument types do not match System.Linq.Expressions.Expression.Condition(Expression test, Expression ....)
- javascript - 在使用 Javascript 上传之前更改音频文件的比特率
- javascript - 在 React 中将多个值和设置器对传递给 Context.Provider
- django - Django 导入错误模块“app1.context_processor”未定义“songs_list”属性/类
- javascript - 从 Angular Web 应用程序广播事件并在 IONIC 应用程序中接收
- docker - kubeadm 初始化问题 - 无法执行 'docker info'
- webstorm - WebStorm 调试器没有命中
- python - 使用 groupby 变换从特定行中减去值
- ruby - 使用 '+' 和 'insert' 在 Ruby 中操作字符串
- tensorboard - Tensorboard 显示标量值的单点