由于混合使用 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
按值接受其参数。这意味着 n
的 MyNodeBase<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();
}
};
我正在试验 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
按值接受其参数。这意味着 n
的 MyNodeBase<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();
}
};