如果空基类影响派生类的布局

Should an empty base class affect the layout of the derived class?

本文关键字:布局 派生 影响 基类 如果      更新时间:2023-10-16

c++标准(引自n3242草案)对子对象[intro.object]有如下规定:

除非对象是位域或基类的子对象为0大小,该对象的地址是它的第一个字节的地址占据了。两个不同的对象,既不是位域也不是基大小为0的类子对象必须有不同的地址。

现在,给定下面的代码片段:

struct empty { };
struct member: empty { };
struct derived: empty { member m; };
int main(void)
{
    printf("%d", sizeof(derived));
    return 0;
}

gcc输出的是2, Visual c++ 2010输出的是1。我怀疑gcc认为该标准的意思是,如果类型代表不同的对象,则不能别名存储类型。我敢打赌,MSVC认为这个标准的意思是,如果一个子对象的大小为零,你就可以做任何你想做的事情。

这是未指定的行为吗?

扩展我之前的评论:

对象由其地址标识。如果比较两个相同类型对象的地址(如指针),如果比较结果相等,则认为这两个指针指向同一个对象。

不同类型的

对象不能以这种方式直接比较,因此允许它们具有相同的地址。一个例子是结构体及其第一个成员。它们不能是同一类型的。基类和派生类也不能,所以如果基类为空,它们可能具有相同的地址。

但是,基类和派生类的第一个成员可以具有相同的类型。这不是问题,除非基类也是空的,并且编译器尝试空基类优化。在这种情况下,指向同一类型的两个不同对象的指针比较相等,因此可以认为它们是同一个对象。

因此,如果成员具有不同的类型(empty和char),它们可以具有相同的地址。如果它们是相同的类型,它们不能,因为这会破坏对对象标识的测试,比如if (this != &that),有时用来测试像自我赋值这样的东西。

顺便说一下,微软同意这是他们编译器中的一个错误,但有其他更紧急的事情要先修复。

这取决于实现。

标准明确允许空基优化,但不要求它。事实上,该标准对内存中类的布局没有太多要求,只要求某些类彼此之间的布局兼容(但不要求通用的布局是什么)。还指定了成员的顺序(当没有中间的可访问性说明符时),但是允许填充、页眉、页脚和各种奇怪的东西。

在c++ 11标准的最终版本中,该段落被修改为:

除非对象是位域或基类的子对象为0大小,该对象的地址是它的第一个字节的地址占据了。两个不是位域的对象可以有相同的如果其中一个是另一个的子主语,或者至少有一个是大小为零的基类子对象,它们具有不同的类型;否则,它们必须有不同的地址。

虽然我不确定我理解这与对象的大小有什么关系

这篇文章有很好的解释。我只是想补充一下,为了解决这个结构膨胀的问题,你可以简单地使empty类成为一个模板,这样用不同的模板参数实例化它就会使它成为一个不同的类:

template<class T>
struct empty { };
struct member: empty<member> { };
struct derived: empty<derived> { member m; };
int main(void)
{
    printf("%dn", sizeof(derived));
    return 0;
}

输出1

这就是在大型项目中避免使用boost::noncopyable的原因。