繁体   English   中英

虚拟继承中相同的最派生类=父类之间的相同偏移量?

[英]Same most-derived class in virtual inheritance = same offset between parent class?

对于某个类F ,它的指针(通过new F()创建)可以向上转换为基类的指针,例如B*C*D*E*

在此输入图像描述

是否保证对于某个编译器(某个配置和某个.exe程序),一对任何提到的类的上转地址(以字节为单位)的差异(例如,在B*C*D*选择2)和new F() )的每个实例的E* )是一个常数吗?

例如,这个MCVE打印8对我来说都是10次: -

#include <iostream>
class B{ public: virtual ~B(){} };
class D: virtual public B{};
class C: virtual public B{};
class E: virtual public D{};
class F: virtual public E, virtual public C{};
int main(){
    for(int n=0;n<10;n++){
        F* f = new F();
        C* c=f;
        E* e=f;
        int offset=
            (reinterpret_cast<uintptr_t>(c))
            -
            (reinterpret_cast<uintptr_t>(e));
        std::cout<<offset<<std::endl;
    }
}

我相信答案是肯定的,因为我可以static_cast它。
(我在很长一段时间内无意识地依赖这个假设。)

不同的编译器可能会打印不同的偏移量,但我只关心某个(相同的) 程序和某个(相同的) 真正的底层类new F() )的情况。

我希望价值在每种情况下始终保持不变。 如果是这样,我不必修复我的程序。

如果回答引用一些C ++规范,我会很高兴。

编辑:修复我的代码中的不一致(感谢curiousguy的评论)

[前言:术语:

为简单起见,我们大致遵循Itanium C ++ ABI并放宽/概括术语:

一个适当的基BD是一个真正的基类,从不同的D 不正确的基础是适当的基础或D本身。

D类的适当子对象是成员或适当的基础; 不正确的主题是一个适当的子对象或D本身。

- 前言]

[前言:错误的假设:

您似乎认为表达类型转换的印象是static_cast以某种方式保证不会生成复杂的代码。 情况并非如此, static_cast可以调用直接初始化的任何内容,包括调用构造函数: static_cast<String>("")

- 前言]

没有合理的理由要求实现不将每个基类子对象放在D类型的完整(或大多数派生,实际上具有相同布局)对象的固定已知偏移量中; 所以问题是: 有没有什么能阻止不正当的实施呢?

实现具有不同的布局需要什么,并且这样的实现是否符合要求? 我们需要列出类层次结构中支持的指针移动(隐式转换或强制转换)。

[注继承的(非静态)数据成员的访问被通过的(隐式)定义的转化this随后的非继承的数据成员的访问,因此是一种遗传性非静态函数的调用。]

对于:

  • XD的(不正确的)基础子对象
  • Y的(正确的)基子X (并不重要,Y是适当的实际)
  • ZD另一个(适当的)基础

ASCII艺术摘要(树可能会折叠):

Y
|
X     Z
 \   /
   D

这些基数必须是明确的:基础子对象Y必须是X唯一的那种类型。 (但是D的间接基数Y不必明确: D::Y可能不指定单个基数,只有D::X::Y必须是明确的。)

必须支持三种“简单”层次结构运动:

  • (上) X*Y*转换(可以隐式完成)
  • (向下NV)对于X的非虚拟基础Y: Y*X*向下转换可以由static_cast执行
  • (向下P)对于(可能是虚拟的)X的多态基数Y: Y*X*向下转换可以通过dynamic_cast执行

其他更复杂的运动是Y*Z*dynamic_cast ; 这是两个动作:向下投射然后向上投射,通过最衍生的对象D ,它不必是静态已知的类型。 (明确执行这两个步骤的代码必须能够命名为D

通常,这些操作是在至少部分构造的对象上执行的。

(C ++标准尚未明确决定是否在指向未构造对象的指针上支持转换为指向非虚拟基础的指针。任何涉及虚拟基础的内容都无法在未构造的对象上完成。)

因此在实践中,(不正确的)子对象X必须携带足够的信息来定位其虚拟基础,通常是通过明确地将其地址放在隐藏成员中,或者通过将它们的偏移存储在vtable中。

[这意味着在构建具有虚拟基础的适当基类期间,vtable通常不能与完整对象的vtable相同。 这与基础子对象(无论是否为虚拟对象)的构造不同,只有非虚拟基础。

如果通过vtable定位虚拟基础,则意味着没有更多可能的类布局,那么存在不同的vtable。 派生类最多的构造函数将无法随机化布局(除非vtables在现场发布到描述所述布局)。

如果通过隐藏的数据成员定位虚拟基础,则看起来存在不正确实现的更大灵活性。 事实上,必须支持来自多态虚拟基础的向下转换这一事实:多态基础只知道其动态类型(通过所有现有实现中的vptr)。 派生类可以存储其基类的地址(或偏移)(某些,任何)的数组, 但是基础不能通过构造存储关于其每个派生类的信息 ,因为必须在知道哪些类将被定义之前定义它的布局。从中得出(很容易看出sizeof(T)不能,即使在最不正确的实现中,也没有在T中使用的类定义的单调函数,并且使用T )。

但是,通过以下任一方法,不正当的实现仍然可以支持多种布局:

(多的vtables)

如果vtables是在现场生成的,或者如果许多vtable是先验地创建以允许具有虚拟基础的类的不同布局,则多态基础可以访问足够的信息来执行任何向下转换。

[注意,uniques类型和vtable之间一直没有一对一的映射,所以typeid的相等测试,即使是相同类型的表达式,也不能成为vptr之间的地址比较。 通常,类型相等是通过比较typeinfo指针来实现的。

[请注意,如果您使用的是“DLL”和动态链接器,各种“等效”(链接前符号定义相同)类型信息表(vtable和typeinfo结构)可能存在于不同的地址,但这些非融合链接断开无论如何,ODR也不会被融合。]

(BLIP)

每个多态潜在的基类 (可以用作基类,因此最终或本地类可以免除)将至少有一个额外的隐藏成员:指向最派生类中的成员的指针(或者相对偏移) :( 基表) ,一个隐藏表非静态成员。

(基表)将列出(一些,任何)基类地址(或偏移量),基本定位符(反常实现想要重新排序的那些基础)。

BLIP (base- locators -info-ptr):一个指向类型的类型结构的指针,它包含基本定位器布局的描述,因为布局取决于在编译时未知的派生类最多的类型时间; 请注意,BLIP可以存储在vtable中,因为它依赖于类型,而不依赖于实例。

向下转换将找到(基表),它是不透明的,不可能由只知道基类的代码解释,然后使用BLIP对其进行解码,就像typeinfo数据包含在基类中导航的代码一样实现向下或向上动态dynamic_cast

这看起来格外复杂,难以做到,而且出于什么目的?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM