由于混合使用 CRTP 和接口而导致崩溃?

Crash due to mixing CRTP & interfaces?

我正在试验 CRTP 并将其与接口混合使用,我无法解释此程序崩溃的原因(在 Clang、GCC 和 MSVC 中)。在最新的 Clang 和 GCC 中,它会在没有警告的情况下使用 -Wall、-Wextra 进行构建。我的猜测是虚拟方法调用未解析,但我无法解释原因(如果从接口中删除 GetInt() 则不会崩溃)。在我的调试器中,我看到崩溃发生在 static_cast<T*>(this)->GetInt().

#include <iostream>

class INode
{
public:
    virtual int GetInt() = 0;
protected:
    ~INode() {}
};

template<template<class> typename NodeBase>
class Node : public INode, public NodeBase<Node<NodeBase>>
{
public:
    int GetInt() override { return 42; }
};

template<typename T>
class MyNodeBase
{
public:
    int CallGetInt() {
        return static_cast<T*>(this)->GetInt();
    }
};

template<template<class> typename NodeBase>
int Foo1(Node<NodeBase> n) {
    return n.CallGetInt();
}

template<typename T>
int Foo2(MyNodeBase<T> n) {
    return n.CallGetInt();
}

int main() {
    Node<MyNodeBase> n;
    std::cout << Foo1(n) << std::endl; // ok
    std::cout << Foo2(n) << std::endl; // crash
}

您正在 slicing n 拨打 Foo2

Foo2 按值接受其参数。这意味着 nMyNodeBase<Node<MyNodeBase>> sub-object 的 copy 会传递给 Foo2。由于 Foo2 中的 n 不是 Node<MyNodeBase>,因此通过从 CallGetInt 中的转换返回的指针调用 GetInt 会导致您的程序表现出未定义的行为。

如果您将 Foo2 更改为通过引用接受其参数,您的程序的行为将是 well-defined。

Foo2 参数按值传递。所以完整的对象 n 是类型 MyNodeBase<Node<MyNodeBase>>。它不是 Node<MyNodeBase> 的派生。但是表达式 static_cast<T*>(this) 假定 this 派生自 Node<MyNodeBase> 因此 static_cast<T*>(this)->GetInt() 是未定义的行为。

你避免了这个错误:

template<typename T>
class MyNodeBase
{
protected:
   MyNodeBase() = default;
   MyNodeBase(const MyNodeBase&) = default;
   MyNodeBase& operator = (const MyNodeBase&) = default;

public:
    
    int CallGetInt() {
        return static_cast<T*>(this)->GetInt();
    }
};