我想不出一个合适的问题标题来描述问题。希望下面的详细信息能清楚地解释我的问题。
考虑下面的代码
#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 基类的接口(interface),例如 call
, 和 call
将发送至call_dispatch
可以是基类默认实现或派生类实现。如果用户定义派生类,这些都可以正常工作,比如 D
, 确实源自 Base<D>
.如果它来自 Base<Unrelated>
将引发编译时错误在哪里 Unrelated
不是源自 Base<Unrelated>
.但它不会阻止用户编写上述代码。
用户通过从基 CRTP 类派生并提供一些实现细节来使用该库。当然还有其他设计方案可以避免上述错误使用的问题(例如抽象基类)。但是,让我们暂时将它们放在一边,相信我,出于某种原因,我需要这种设计。
所以我的问题是,有什么方法可以防止用户编写错误的派生类,如上所示。也就是说,如果用户编写派生实现类,例如 D
, 但他是从 Base<OtherD>
推导出来的,则应引发编译时错误。
一种解决方案是使用 dynamic_cast
.但是,这很广泛,即使它有效,它也是一个运行时错误。
最佳答案
1) 将 Base 的所有构造函数设为私有(private)(如果没有构造函数,则添加一个)
2) 将 Derived 模板参数声明为 Base 的友元
template <class Derived>
class Base
{
private:
Base(){}; // prevent undesirable inheritance making ctor private
friend Derived; // allow inheritance for Derived
public :
void call ()
{
static_cast<Derived *>(this)->call_impl();
}
};
在此之后,将不可能创建错误继承 D2 的任何实例。
关于c++ - 防止用户从不正确的 CRTP 基础派生,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11224838/