问题描述
我正在试验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)
假定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();
}
};