如何在CRTP中实现编译时检查向下转型是否有效?

9

我有一个普通的CRPT(请不要被访问限制所分散注意力 - 问题与此无关):

 template<class Derived>
 class Base {
     void MethodToOverride()
     {
        // generic stuff here
     }
     void ProblematicMethod()
     {
         static_cast<Derived*>(this)->MethodToOverride();
     } 
 };

这通常被用于以下方式:

 class ConcreteDerived : public Base<ConcreteDerived> {
     void MethodToOverride()
     {
        //custom stuff here, then maybe
        Base::MethodToOverride();
     }
 };

现在我对static_cast感到困扰。我需要一个向下转型(而不是向上转型),因此我必须使用显式转换。在所有合理的情况下,转换将是有效的,因为当前对象确实是派生类的。

但是,如果我以某种方式更改了层次结构,那么转换现在将变得无效吗?

我是否可以强制执行编译时检查,以确保此情况下显式向下转换是有效的?


1
在基类中不需要有一个MethodToOverride。 - ysdx
1
@ysdx:如果我想让它可选地被覆盖或者有一些共同的实现,而且我确实想要这样做。 - sharptooth
1
但是如果你在基类中有这个函数,调用将始终“有效”,因为有一个可以调用的函数。 - Bo Persson
1
你考虑过使用虚函数吗? :-) - Bo Persson
例如,我可能会意外地将一些不相关的类作为模板参数传递。 - sharptooth
显示剩余6条评论
5个回答

5

在编译时,您只能检查静态类型,这就是 static_cast 已经实现的功能。

给定一个 Base*,只有在运行时才能知道它的 动态 类型,也就是它实际上指向了一个 ConcreteDerived 还是其他什么东西。因此,如果您想要检查这个类型,必须在运行时进行(例如使用 dynamic_cast


1
但是,要使用dynamic_cast就需要一些虚函数,而我认为这个结构试图避免这种情况。 - Bo Persson
我的观点只是这个检查只能在运行时执行,这会带来相关的成本,正如你所指出的。 - jalf

4
为了更加安全,您可以给Base类添加一个受保护的构造函数,以确保有些东西是从它派生出来的。这样,唯一的问题就是真的很愚蠢的人:
class ConcreteDerived : public Base<SomeOtherClass>

但这应该被第一次代码审查或测试用例捕捉到。


3

进一步说明@Bo Persson所说的,你可以在该构造函数中使用Boost.TypeTraits或C++0x/11 <type_traits>进行编译时检查:

#include <type_traits>

template<class Derived>
struct Base{
  typedef Base<Derived> MyType;

  Base(){
    typedef char ERROR_You_screwed_up[ std::is_base_of<MyType,Derived>::value ? 1 : -1 ];
  }
};

class ConcreteDerived : public Base<int>{
};

int main(){
  ConcreteDerived cd;
}

完整示例请参见 Ideone 网站


1
请问你能否把F-word替换成“你搞砸了”,这样就不会有大量的踩和举报了吗? - sharptooth
这里有一个小问题,问题不仅在于确保参数Derived有效地派生自Base<..>,而且还要确保this的当前值有效地是Derived(或其派生类型),这只能在运行时检查。 - Matthieu M.
@Matthieu:我的回答是对@Bo Personn的扩展,所以他关于受保护构造函数的说法也适用于我。如果“this”不是派生自其他内容,则存在非常严重的问题。 - Xeo
我同意在CRTP中this应该没问题,但问题是在需要检测它不正常的情况下。 - Matthieu M.
不幸的是,这种情况无法捕获两个类从相同的CRTP基类派生的情况,其中一个类错误地传递了另一个类,例如 class ConcreteDerived1:public Base <ConcreteDerived1> {}; class ConcreteDerived2:public Base <ConcreteDerived1> {}; 更糟糕的是,对Derived进行static_cast仍将编译通过,因为编译器“知道如何”进行向下转换。 - golvok

2

似乎存在一种在编译时检查CRPT正确性的方法。

通过使基类(Base)抽象化(添加一些纯虚方法到Base中),我们可以保证任何Base实例都是某个派生实例的一部分。

通过将所有Base构造函数设置为私有,我们可以防止从Base进行不必要的继承。

通过将Derived声明为Base的友元,我们允许CRPT所期望的唯一继承。

这样,CRPT下转换应该是正确的(因为一些内容是从基类继承下来的,而这“些内容”可能只是Derived,而不是其他类)。

也许出于实际目的,第一步(使Base抽象化)是多余的,因为成功的static_cast保证了Derived在Base层次结构中的位置。这将仅产生一个奇怪的错误,如果Derived从Base <Derived> (正如CRPT所期望的那样)继承,但同时Derived在Derived代码中创建另一个Base <derived> 实例(没有继承)。但是,我怀疑有人会意外地编写这种奇怪的代码。


1

当你像下面这样做时:

struct ConcreteDerived : public Base<Other>  // Other was not inteded

你可以创建派生或基类的class对象。但是如果尝试调用函数,则会出现与static_cast相关的编译错误。在我看来,这将满足所有实际情况。

如果我正确理解了问题,那么我认为答案就在你的问题本身中。:)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接