您可以将“this”静态转换为基类构造函数中的派生类,然后稍后使用结果吗?

D C*_*zee 6 c++ constructor static-cast language-lawyer

我们在工作中的代码库中遇到了这种情况,并且我们就这是否是有效的 C++ 进行了激烈的争论。这是我能想到的最简单的代码示例:

template <class T>
class A {
public:
    A() { subclass = static_cast<T*>(this); }
    virtual void Foo() = 0;
protected:
    T* subclass;
};

class C : public A<C> {
public:
    C(int i) : i(i) { }
    virtual void Foo() { subclass->Bar(); }
    void Bar() { std::cout << "i is " << i << std::endl; }
private:
    int i;
};

int main() {
    C c(5);
    c.Foo();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这段代码在实践中 100% 有效(只要模板参数类型与子类类型匹配),但是如果我们通过运行时分析器运行它,它会告诉我们 是static_cast无效的,因为我们正在转换this为 a C*,但是C构造函数尚未运行。果然,如果我们将 更改static_cast为 a dynamic_cast,它就会返回,并且在访问innullptr时该程序将失败并崩溃。iBar()

我的直觉是,应该总是可以在不破坏代码的情况下替换为static_castdynamic_cast这表明原始代码实际上取决于编译器特定的未定义行为。然而,在 cppreference 上它说:

如果对象表达式引用或指向的实际上是 D 类型对象的基类子对象,则结果引用 D 类型的封闭对象。

问题是,在类型对象构造完成D之前,它是类型对象的基类子对象吗?D或者这是未定义的行为?我的 C++ 规则律师水平还不足以解决这个问题。

Bri*_*ian 7

在我看来,根据标准的当前措辞,这是明确定义的:C对象在 时存在static_cast,尽管它正在构造中并且其生命周期尚未开始。这似乎static_cast根据 [expr.static.cast]/11 进行了明确定义,其中部分内容为:

\n
\n

...如果cv1 \xe2\x80\x9d 类型的B\xe2\x80\x9c 指针的纯右值指向 aB实际上是类型 的对象的基类子对象D,则生成的指针指向类型 的封闭对象D。否则,行为是未定义的。

\n
\n

它并没有说D对象的生命周期一定已经开始。

\n

我们可能还想看看关于何时执行从派生到基类的隐式转换变得合法的显式规则,[class.cdtor]/3

\n
\n

显式或隐式地将引用类对象的指针(泛左值)转换为指向 的直接或间接基类的X指针(引用),以及直接或间接构建其所有直接或间接基类的指针(引用)派生自应已开始,并且这些类的销毁尚未完成,否则转换会导致未定义的行为。要形成指向对象的直接非静态成员(或访问其值)的指针, 的构造应已开始,且其销毁不应完成,否则指针值的计算(或访问成员值)导致未定义的行为。BXXBobjobj

\n
\n

根据这条规则,一旦编译器开始构造基类A<C>,就可以明确地从C*到进行隐式转换A<C>*。在此之前,它会导致 UB。其原因基本上与虚拟基类有关:如果A<C>继承的路径C包含任何虚拟继承,则转换可能依赖于链中构造函数之一设置的数据。对于从基类到派生类的转换,如果链上确实存在任何虚拟继承,static_cast则将无法编译,因此我们实际上不需要问自己这个问题,但是这些数据足以用于反方向吗?

\n

我真的看不到标准文本中的任何内容,也看不到任何基本原理,因为在static_cast您的示例中没有明确定义,也没有static_cast在反向隐式转换(或static_cast)时从基数到派生的任何其他情况下被允许(虚拟继承的情况除外,正​​如我之前所说,无论如何都会导致编译错误)。

\n

(是否可以更早地进行明确定义?在大多数情况下,这是不可能的;在允许从to转换之前,您怎么可能尝试static_castB*to 到,而没有通过执行后者来精确获取指针? 如果答案是你从 到 是通过一个中间基类得到的,该中间基类的构造函数已经开始,但还有另一个中间基类共享同一个基类子对象,并且它的构造尚未开始,那么它是一个虚拟基类,再说一次,这意味着编译器将阻止您尝试从回退到。所以我认为这里没有任何问题需要解决。)D*D*B*B*D*B*C1C2BBstatic_castB*D*

\n