将指向部分构造对象的指针转换为指向基类的指针是不是合法?

Posted

技术标签:

【中文标题】将指向部分构造对象的指针转换为指向基类的指针是不是合法?【英文标题】:Is it legal to cast a pointer to a partially constructed object to a pointer to a base class?将指向部分构造对象的指针转换为指向基类的指针是否合法? 【发布时间】:2021-08-07 01:57:56 【问题描述】:

也就是说,这样的事情总是合法的吗?

struct Derived;
struct Base  Base(Derived*); ;
struct Derived : Base  Derived() : Base(this)   ;
Base::Base(Derived *self) 
    if(static_cast<Base*>(self) != this) std::terminate();


int main() 
    Derived d; // is this well-defined to never call terminate?

在评估 static_cast 时,self 尚未指向 Derived 对象——该对象正在构建中。例如。如果Derived 有数据成员,它们的构造函数就不会被调用。是否仍保证强制转换为已定义行为,从而产生与 Basethis 等效的指针(它确实指向完全构造的 Base 基类子对象)?

我认为接近回答这个问题的标准引用是[conv.ptr]/3

...转换的结果是指向派生类对象的基类子对象的指针。 ...

但我认为还没有派生类对象,那么会发生什么?如果确实未定义,self != static_cast&lt;Derived*&gt;(this) 的答案是否会改变?

(Clang 和 GCC “按预期”编译和运行它。)

【问题讨论】:

有 2 个子问题:自我保证等于这个,并且取消引用 static_cast&lt;Base*&gt;(self) 是明确定义的。 在评估static_cast 时,self 还没有指向Derived 对象 为什么?那么self指向哪里呢? @LanguageLawyer 它指向未初始化的存储,Derived 对象最终将开始其生命周期,不是吗?我的报价是否有可能适用于不在其生命周期内的对象? 指向未初始化的存储 timsong-cpp.github.io/cppwp/n4868/basic.compound#3.sentence-8 是什么意思? 我的引用是否有可能适用于不在其生命周期内的对象?很遗憾,不能,因为timsong-cpp.github.io/cppwp/n4868/basic.life#6.4 @LanguageLawyer 也许我最好将其改写为“指针指向不在其生命周期内的对象”。我猜该标准并没有真正定义对象“存在”的含义。另外,我不确定该段的限制是否完全适用:它说对于正在构建的对象的情况,您可以转到此处答案中引用的子条款(“否则”)。或者你说它仍然适用?那么Base *ptr = self; if(self != this) std::terminate(); 是正确的而问题中的代码不是吗? 【参考方案1】:

这很好:[class.cdtor]/3

要显式或隐式地将引用类X的对象的指针(glvalue)转换为指向X的直接或间接基类B的指针(引用),X的构造并且从B 直接或间接派生的所有直接或间接基础的构建应已开始,并且这些类的销毁不应完成,否则转换会导致未定义的行为。 ...

它要求源类型(以及从目标类型继承的任何其他基类)开始它的构造函数并且没有完成它的析构函数。即使是基类的初始化器也算作派生构造器的开始;该标准包含一个非常相似的示例。

【讨论】:

CWG1517 提议的决议指出,在基础子对象构建完成后,构建周期开始。另一方面,该决议将第 3 段更改为仅适用于虚拟基地。

以上是关于将指向部分构造对象的指针转换为指向基类的指针是不是合法?的主要内容,如果未能解决你的问题,请参考以下文章

关于C++基类、派生类的引用和指针

从指向基类的指针调用派生类的复制构造?

向上强制转换和向下强制转换

包含指向派生模板类的基类指针的类的赋值运算符和复制构造函数

为啥C ++允许将指向基对象的指针转换为派生类的指针[重复]

将基类指针强制转换为子类指针后的疑惑