当空基类也是成员变量时,为什么禁止空基优化?
Why is empty base optimization forbidden when the empty base class is also a member variable?
空基优化很棒。 但是,它带有以下限制:
如果其中一个空基类也是第一个非静态数据成员的类型或类型的基,则禁止空基优化,因为同一类型的两个基子对象在派生最多的类型的对象表示形式中需要具有不同的地址。
若要解释此限制,请考虑以下代码。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
与包含它的Bar
实例的地址相同——因为它在其他情况下需要(1(。毕竟,我对virtual
继承没有做任何花哨的事情,无论如何基类都是空的,并且使用Base2
的编译表明在这种特殊情况下没有任何中断。
但显然,这种推理在某种程度上是不正确的,并且还有其他情况需要这种限制。
假设答案应该是 C++11 或更高版本(我目前正在使用 C++17(。
(1(注意:EBO在C++11年升级,特别是成为StandardLayoutType
的强制性要求(尽管上面的Bar
不是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 << "Hellon";}
};
struct Foo : Base {
void hi() override { std::cout << "Guten Tagn";}
};
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
但是,如果我们有空的基类优化,则最后两个是相同的!
- 为什么我会收到此警告:ISO c++ 禁止可变长度数组"v"[-Wvla]
- 当空基类也是成员变量时,为什么禁止空基优化?
- 为什么禁止建造 istreams?
- "Empty base optimization" lambda 捕获 - 标准禁止?为什么?
- 为什么std::变体中禁止引用
- 为什么 C 与 C++ 相反,禁止在指针到指针的两个级别添加 const 限定
- C :为什么禁止递归模板别名
- 为什么默认成员值禁止使用支持列表初始化
- 为什么ISO C++标准禁止对成员进行初始化
- 为什么禁止c++中的静态分配
- 为什么隐式"lambda to function pointer conversion"禁止"by reference"捕获静态成员?
- C++ 外部类访问 内部类为什么被禁止
- 为什么数组形式参数总是隐式转换为指针而不是被禁止
- 为什么禁止在 mac 中使用没有 exec 的 fork
- 为什么 ISO C++禁止返回数组
- 为什么禁止对位域进行非常量引用
- 为什么C++禁止新的T[n](arg..)
- 为什么C++禁止声明没有类型的参数
- 为什么标准禁止部分专门化的友元声明
- 类成员变量的Decltype是否被禁止?为什么