我无法想出一个适当的问题标题来描述问题.希望下面的细节清楚地解释了我的问题.
请考虑以下代码
#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();
}
Run Code Online (Sandbox Code Playgroud)
它会编译并运行,但定义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>
地方派生的,它将引发编译时错误.但它不会阻止用户编写如上所述的代码.Unrelated
Base<Unrelated>
用户通过从基础CRTP类派生并提供一些实现细节来使用库.当然还有其他设计方案可以避免上述错误使用的问题(例如抽象基类).但是让我们暂时把它们放在一边,只是因为某些原因而相信我需要这个设计.
所以我的问题是,有什么办法可以阻止用户编写不正确的派生类,如上所述.也就是说,如果用户编写派生的实现类,比如说D
,但是他从中派生出来Base<OtherD>
,则应该引发编译时错误.
一种解决方案是使用dynamic_cast
.但是,这是广泛的,即使它工作,它是一个运行时错误.
use*_*672 33
1)使所有Base的构造函数私有(如果没有构造函数,添加一个)
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();
}
};
Run Code Online (Sandbox Code Playgroud)
在此之后,将无法创建错误的继承D2的任何实例.
如果你有 C++11 可用,你可以使用static_assert
(如果没有,我相信你可以用 boost 模拟这些东西)。你可以断言例如is_convertible<Derived*,Base*>
或is_base_of<Base,Derived>
。
这一切都发生在 Base 中,它所拥有的只是 Derived 的信息。它永远不会有机会看到调用上下文是来自 D2 还是 D1,因为这没有区别,因为Base<D1>
以一种特定的方式实例化一次,无论它是由 D1 还是 D2 派生自它(或由用户显式实例化它)。
由于您不想(可以理解,因为它有时具有显着的运行时成本和内存开销)使用 dynamic_cast,请尝试使用通常称为“poly cast”的东西(boost 也有自己的变体):
template<class R, class T>
R poly_cast( T& t )
{
#ifndef NDEBUG
(void)dynamic_cast<R>(t);
#endif
return static_cast<R>(t);
}
Run Code Online (Sandbox Code Playgroud)
通过这种方式在您的调试/测试构建中检测到错误。虽然不是 100% 保证,但在实践中,这通常会捕获人们所犯的所有错误。