是否由于混合CRTP和接口而导致崩溃?

问题描述

我正在试验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
}

解决方法

您在n的通话中slicing Foo2

Foo2按值接受其参数。这意味着n的{​​{1}}子对象的副本是传递给MyNodeBase<Node<MyNodeBase>>的对象。由于Foo2中的n不是Foo2,因此通过Node<MyNodeBase>中的类型转换返回的指针调用GetInt会导致程序表现出不确定的行为。>

如果将CallGetInt更改为通过引用接受其参数,则程序的行为将得到明确定义。

,

Foo2参数是按值传递的。因此,完整对象n的类型为MyNodeBase<Node<MyNodeBase>>。它不是Node<MyNodeBase>的派生。但是表达式static_cast<T*>(this)假定thisNode<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();
    }
};

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...