简体   繁体   English

由于混合 CRTP 和接口而崩溃?

[英]Crash due to mixing CRTP & interfaces?

I am experimenting with CRTP and mixing it with interfaces, and I cannot explain why this program crashes (in Clang, GCC and MSVC).我正在试验 CRTP 并将其与接口混合,但我无法解释为什么该程序会崩溃(在 Clang、GCC 和 MSVC 中)。 In latest Clang and GCC it builds without warning with -Wall, -Wextra.在最新的 Clang 和 GCC 中,它在没有警告的情况下使用 -Wall、-Wextra 构建。 My guess is that the virtual method call is not resolved but I cannot explain why (no crash if GetInt() is removed from the interface).我的猜测是虚拟方法调用没有解决,但我无法解释原因(如果从接口中删除 GetInt() 不会崩溃)。 In my debugger I see that the crash happens on line static_cast<T*>(this)->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
}

You're slicing n in your call to Foo2 .您在对Foo2的调用中对n进行切片

Foo2 accepts its parameter by value. Foo2按值接受其参数。 That means that a copy of n 's MyNodeBase<Node<MyNodeBase>> sub-object is what gets passed to Foo2 .这意味着nMyNodeBase<Node<MyNodeBase>>子对象的副本是传递给Foo2 Since n in Foo2 is not a Node<MyNodeBase> , calling GetInt through the pointer returned from the cast in CallGetInt results in your program exhibiting undefined behavior.由于Foo2中的n不是Node<MyNodeBase> ,因此通过从CallGetInt中的转换返回的指针调用GetInt会导致您的程序表现出未定义的行为。

If you change Foo2 to accept its parameter by reference, you program's behavior would be well-defined.如果您更改Foo2以通过引用接受其参数,则您的程序的行为将是明确定义的。

Foo2 argument is passed by value. Foo2参数按值传递。 So the complete object n is of type MyNodeBase<Node<MyNodeBase>> .所以完整的 object nMyNodeBase<Node<MyNodeBase>>类型。 It is not a derived of Node<MyNodeBase> .它不是Node<MyNodeBase>的派生。 But the expression static_cast<T*>(this) assumes that this is derived of Node<MyNodeBase> thus static_cast<T*>(this)->GetInt() is undefined behavior.但是表达式static_cast<T*>(this)假定this是从Node<MyNodeBase>派生的,因此static_cast<T*>(this)->GetInt()是未定义的行为。

You avoid this error:您可以避免此错误:

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();
    }
};

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

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