我想不出一个合适的问题标题来描述这个问题。希望下面的详细信息可以清楚地解释我的问题。
考虑以下代码
#include <iostream>
template <typename Derived>
class Base
{
public :
void call ()
{
static_cast<Derived *>(this)->call_impl();
}
};
class D1 : public Base<D1>
{
public :
void call_impl ()
{
data_ = 100;
std::cout << data_ << std::endl;
}
private :
int data_;
};
class D2 : public Base<D1> // This is wrong by intension
{
public :
void call_impl ()
{
std::cout << data_ << std::endl;
}
private :
int data_;
};
int main ()
{
D2 d2;
d2.call_impl();
d2.call();
d2.call_impl();
}
尽管 的定义D2
是故意错误的,但它会编译并运行。第一次调用将输出一些预期未初始化d2.call_impl()
的随机位。D2::data_
第二次和第三次调用都将输出100
.data_
我明白为什么它会编译和运行,如果我错了,请纠正我。
当我们进行调用d2.call()
时,调用被解析为Base<D1>::call
,并且将this
转换为D1
和调用D1::call_impl
。因为D1
确实是派生形式Base<D1>
,所以在编译时强制转换很好。
在运行时,在强制转换之后this
,虽然它确实是一个D2
对象,但它被视为好像它是D1
,并且调用D1::call_impl
将修改应该是的内存位D1::data_
,并输出。在这种情况下,这些位恰好在哪里D2::data_
。我认为第二个d2.call_impl()
也应该是未定义的行为,具体取决于 C++ 实现。
关键是,这段代码虽然本质上是错误的,但不会给用户任何错误的迹象。我在我的项目中真正做的是我有一个 CRTP 基类,它就像一个调度引擎。库中的另一个类访问 CRTP 基类的接口,比如call
,call
并将调度到call_dispatch
该接口,可以是基类默认实现或派生类实现。如果用户定义的派生类,比如说D
,确实是从Base<D>
. 如果它源自Base<Unrelated>
whereUnrelated
不是源自Base<Unrelated>
. 但它不会阻止用户编写上述代码。
用户通过从基 CRTP 类派生并提供一些实现细节来使用该库。当然还有其他设计方案可以避免上述错误使用的问题(例如抽象基类)。但是让我们暂时把它们放在一边,相信我因为某种原因我需要这个设计。
所以我的问题是,有什么方法可以防止用户编写错误的派生类,如上所示。也就是说,如果用户编写了一个派生的实现类,比如D
,但他是从 派生的Base<OtherD>
,那么将引发编译时错误。
一种解决方案是使用dynamic_cast
. 但是,这是广泛的,即使它有效,它也是一个运行时错误。