为什么空基class也是成员变量时禁止空基优化?
Why is empty base optimization forbidden when the empty base class is also a member variable?
Empty base optimization 太棒了。但是,它有以下限制:
Empty base optimization is prohibited if one of the empty base classes is also the type or the base of the type of the first non-static data member, since the two base subobjects of the same type are required to have different addresses within the object representation of the most derived type.
要解释此限制,请考虑以下代码。 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
的基数 class 对 foo
类型的基数 class 很重要,反之亦然。
事实上,如果有的话,我希望 &foo
与包含它的 Bar
实例的地址相同——因为在其他情况下需要它 (1)。毕竟,我没有对 virtual
继承做任何花哨的事情,不管怎样,基础 classes 都是空的,并且 Base2
的编译表明在这种特殊情况下没有任何中断。
但显然这种推理在某种程度上是不正确的,还有其他情况需要这种限制。
假设答案应该针对 C++11 或更新版本(我目前使用的是 C++17)。
(1) 注意:EBO 在 C++11 中升级,特别是 StandardLayoutType
s(尽管上面的 Bar
不是 StandardLayoutType
)。
好吧,好像我一直都错了,因为对于我的所有示例,都需要存在一个用于基础对象的 vtable,这将防止空基础优化开始。我将保留这些示例,因为我认为它们提供了一些有趣的示例,说明为什么拥有唯一地址通常是一件好事。
经过更深入的研究,当第一个成员与空基 class 的类型相同时,没有技术原因可以禁用空基 class 优化。这只是当前 C++ 对象模型的 属性。
但在 C++20 中会有一个新属性 [[no_unique_address]]
告诉编译器非静态数据成员可能不需要唯一地址(从技术上讲它 可能重叠 [intro.object]/7)。
这意味着(强调我的)
The non-static data member can share the address of another non-static data member or that of a base class, [...]
因此可以通过给第一个数据成员属性 [[no_unique_address]]
来 "reactivate" 空基数 class 优化。我添加了一个示例 here 来说明这个(以及我能想到的所有其他情况)是如何工作的。
错误的例子通过这个
既然一个空的class好像没有虚方法,那我再补充第三个例子:
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
但是最后两次调用是一样的
老例子(可能不回答这个问题,因为空 classes 似乎不包含虚拟方法)
在上面的代码(添加了虚拟析构函数)中考虑以下示例
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
但是如果我们有空基 class 优化,最后两个是相同的!
Empty base optimization 太棒了。但是,它有以下限制:
Empty base optimization is prohibited if one of the empty base classes is also the type or the base of the type of the first non-static data member, since the two base subobjects of the same type are required to have different addresses within the object representation of the most derived type.
要解释此限制,请考虑以下代码。 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
的基数 class 对 foo
类型的基数 class 很重要,反之亦然。
事实上,如果有的话,我希望 &foo
与包含它的 Bar
实例的地址相同——因为在其他情况下需要它 (1)。毕竟,我没有对 virtual
继承做任何花哨的事情,不管怎样,基础 classes 都是空的,并且 Base2
的编译表明在这种特殊情况下没有任何中断。
但显然这种推理在某种程度上是不正确的,还有其他情况需要这种限制。
假设答案应该针对 C++11 或更新版本(我目前使用的是 C++17)。
(1) 注意:EBO 在 C++11 中升级,特别是 StandardLayoutType
s(尽管上面的 Bar
不是 StandardLayoutType
)。
好吧,好像我一直都错了,因为对于我的所有示例,都需要存在一个用于基础对象的 vtable,这将防止空基础优化开始。我将保留这些示例,因为我认为它们提供了一些有趣的示例,说明为什么拥有唯一地址通常是一件好事。
经过更深入的研究,当第一个成员与空基 class 的类型相同时,没有技术原因可以禁用空基 class 优化。这只是当前 C++ 对象模型的 属性。
但在 C++20 中会有一个新属性 [[no_unique_address]]
告诉编译器非静态数据成员可能不需要唯一地址(从技术上讲它 可能重叠 [intro.object]/7)。
这意味着(强调我的)
The non-static data member can share the address of another non-static data member or that of a base class, [...]
因此可以通过给第一个数据成员属性 [[no_unique_address]]
来 "reactivate" 空基数 class 优化。我添加了一个示例 here 来说明这个(以及我能想到的所有其他情况)是如何工作的。
错误的例子通过这个
既然一个空的class好像没有虚方法,那我再补充第三个例子:
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
但是最后两次调用是一样的
老例子(可能不回答这个问题,因为空 classes 似乎不包含虚拟方法)
在上面的代码(添加了虚拟析构函数)中考虑以下示例
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
但是如果我们有空基 class 优化,最后两个是相同的!