在 CRTP 基类构造函数中向下转换为派生类:UB 与否?

ttt*_*apa 14 c++ crtp downcast undefined-behavior language-lawyer

考虑以下类:

template <class Derived>
class BaseCRTP {
  private:
    friend class LinkedList<Derived>;
    Derived *next = nullptr;

  public:
    static LinkedList<Derived> instances;

    BaseCRTP() {
        instances.insert(static_cast<Derived *>(this));
    }
    virtual ~BaseCRTP() {
        instances.remove(static_cast<Derived *>(this));
    }
};
Run Code Online (Sandbox Code Playgroud)
struct Derived : BaseCRTP<Derived> {
    int i;
    Derived(int i) : i(i) {}
};
Run Code Online (Sandbox Code Playgroud)
int main() {
    Derived d[] = {1, 2, 3, 4};
    for (const Derived &el : Derived::instances) 
        std::cout << el.i << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

我知道,这是不确定的行为来访问的成员DerivedBaseCRTP<Derived>构造函数(或析构函数),因为Derived构造函数执行BaseCRTP<Derived>构造(和析构函数的其他方式)。

我的问题是:在不访问任何成员的情况下this指针转换Derived *为将其存储在链表中是否是未定义的行为Derived

LinkedList::insert只访问BaseCRTP::next.

使用时-fsanitize=undefined,我确实收到了static_casts的运行时错误,但我不知道它是否有效:


    instances.insert(static_cast<Derived *>(this));

crt-downcast.cpp:14:26: runtime error: downcast of address 0x7ffe03417970 which does not point to an object of type 'Derived'
0x7ffe03417970: note: object is of type 'BaseCRTP<Derived>'
 82 7f 00 00  00 2d 93 29 f3 55 00 00  00 00 00 00 00 00 00 00  e8 7a 41 03 fe 7f 00 00  01 00 00 00
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'BaseCRTP<Derived>'
4
3
2
1

    instances.remove(static_cast<Derived *>(this));

crt-downcast.cpp:17:26: runtime error: downcast of address 0x7ffe034179b8 which does not point to an object of type 'Derived'
0x7ffe034179b8: note: object is of type 'BaseCRTP<Derived>'
 fe 7f 00 00  00 2d 93 29 f3 55 00 00  a0 79 41 03 fe 7f 00 00  04 00 00 00 f3 55 00 00  08 c0 eb 51
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'BaseCRTP<Derived>'
Run Code Online (Sandbox Code Playgroud)

此外,这是LinkedList该类的简化版本:


    instances.insert(static_cast<Derived *>(this));

crt-downcast.cpp:14:26: runtime error: downcast of address 0x7ffe03417970 which does not point to an object of type 'Derived'
0x7ffe03417970: note: object is of type 'BaseCRTP<Derived>'
 82 7f 00 00  00 2d 93 29 f3 55 00 00  00 00 00 00 00 00 00 00  e8 7a 41 03 fe 7f 00 00  01 00 00 00
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'BaseCRTP<Derived>'
4
3
2
1

    instances.remove(static_cast<Derived *>(this));

crt-downcast.cpp:17:26: runtime error: downcast of address 0x7ffe034179b8 which does not point to an object of type 'Derived'
0x7ffe034179b8: note: object is of type 'BaseCRTP<Derived>'
 fe 7f 00 00  00 2d 93 29 f3 55 00 00  a0 79 41 03 fe 7f 00 00  04 00 00 00 f3 55 00 00  08 c0 eb 51
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'BaseCRTP<Derived>'
Run Code Online (Sandbox Code Playgroud)

Ber*_*nns 1

到目前为止,我还没有找到一条规则来说明它是或不是 UB,但我可以解释为什么你会看到观察到的行为。

\n

在 C++ 标准中,我们有以下规则:

\n
\n

可以在构造或销毁期间调用成员函数,包括虚函数 (11.7.2)\n(11.10.2)。当从构造函数或析构函数直接或间接调用虚函数时,\n包括在类\xe2\x80\x99s非静态数据成员的构造或析构期间,并且调用所适用的对象\n是对象(称为 x)在构造或销毁时,调用的函数是 constructor\xe2\x80\x99s 或 destructor\xe2\x80\x99s 类中的最终重写器,而不是在更派生的类中重写它的函数

\n
\n

这基本上是说在构造过程中允许调用虚拟函数。但这些调用不得解析为更多的派生类。

\n

这是通过在构建过程中简单地交换 VMT 指针而在 clang 中实现的。

\n

可以在这里观察到: https: //godbolt.org/z/3Gebvv \n(或者简单地通过分析生成的程序集)

\n

-fsanitize=undefined现在添加一些逻辑来查找错误/未定义的行为。例如,他们检查这样的转换是否有效。为此,他们使用由编译器更改的 VMT。

\n

在非构造函数上下文中,不适当的 vmt 指针是未定义行为的一个很好的指示。但这也可能是消毒剂中的一个错误。

\n