【问题标题】:How to implement a compile-time check that a downcast is valid in a CRTP?如何在 CRTP 中实现向下转换是否有效的编译时检查?
【发布时间】:2011-05-06 06:41:44
【问题描述】:

我有一个普通的旧 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 困扰着我。我需要一个向下转换(不是向上转换),所以我必须使用显式转换。在所有合理的情况下,转换都是有效的,因为当前对象确实属于派生类。

但是,如果我以某种方式更改了层次结构并且演员现在变得无效怎么办?

我可以以某种方式强制编译时检查显式向下转换在这种情况下是否有效?

【问题讨论】:

  • Base 类中不需要 MethodToOverride。
  • @ysdx:如果我希望它可以选择性地被覆盖或有一些常见的实现,我需要它。
  • 但是如果你在基类中有这个函数,这个调用将永远“工作”,因为有一个函数可以调用。
  • @Bo Persson:是的,这就是为什么我需要向下转换来调用最衍生的函数,并且如果需要,最衍生的函数可以调用基本版本。
  • 你不考虑使用虚函数吗? :-)

标签: c++ casting crtp static-cast upcasting


【解决方案1】:

在编译时您只能检查静态类型,而 static_cast 已经这样做了。

给定一个Base*,它只是并且只能在运行时知道它的动态 类型是什么,也就是说,它是否实际上指向ConcreteDerived 或其他东西别的。所以如果你想检查这个,它必须在运行时完成(例如通过使用dynamic_cast

【讨论】:

  • 但是要使用 dynamic_cast 你需要有一些虚函数,我相信这个构造试图避免。
  • 真的。我的观点是,这个检查只能在运行时执行,这会带来相关的成本,正如你所指出的那样
【解决方案2】:

为了更加安全,您可以向 Base 添加一个受保护的构造函数,以确保 something 是从它派生的。那么唯一的问题就是真正愚蠢的人:

class ConcreteDerived : public Base<SomeOtherClass>

但这应该在第一次代码审查或测试用例中发现。

【讨论】:

    【解决方案3】:

    要扩展 @Bo Persson 所说的内容,您可以使用例如 Boost.TypeTraits 或 C++0x/11 &lt;type_traits&gt; 在所述构造函数中进行编译时检查:

    #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;
    }
    

    完整示例on Ideone

    【讨论】:

    • 能否请您将 F 字替换为“你搞砸了”,这样就不会像雨一样投下反对票和旗帜?
    • 这里的小问题,问题不仅在于确保参数Derived有效地派生自Base&lt;..&gt;,而且this的当前值实际上是Derived(或它的派生类型),您只能在运行时检查。
    • @Matthieu:我的回答是对@Bo Personn 的扩展,所以他所说的受保护构造函数也适用于我。然后,如果this 不是派生的,那就是非常非常错误的事情。
    • 我同意在 CRTP 中 this 应该没问题,但问题是寻求一种方法来检测何时不是。
    • 不幸的是,这并没有捕捉到两个类派生自同一个 CRTP 基础的情况,其中一个类错误地传递了另一个类,例如class ConcreteDerived1 : public Base&lt;ConcreteDerived1&gt;{ }; class ConcreteDerived2 : public Base&lt;ConcreteDerived1&gt;{ };。更糟糕的是,static_castDerived 仍然可以编译,因为编译器“知道如何”进行向下转换。
    【解决方案4】:

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

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

    通过将所有 Base 构造函数设为私有,我们可以防止来自 Base 的不良继承。

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

    在此之后,CRPT 向下转换应该是正确的(因为某些东西是从基类继承的,而这个“东西”可能只是派生的,而不是其他一些类)

    也许出于实际目的,第一步(使 Base 抽象)是多余的,因为成功的 static_cast 保证 Derived 位于 Base 层次结构中的某个位置。如果 Derived 继承自 Base &lt;Derived&gt; (如 CRPT 所期望的那样),则仅允许出现异常错误,但同时 Derived 在 Derived 代码的某处创建 Base &lt;derived&gt; 的另一个实例(没有继承)(它可以,因为它是朋友) .不过,我怀疑有人会不小心写出如此奇特的代码。

    【讨论】:

      【解决方案5】:

      当您执行以下操作时:

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

      您可以创建class(派生或基础)的对象。但是如果你尝试调用这个函数,它只会给出与static_cast 相关的编译错误。恕我直言,它将满足所有实际场景。

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

      【讨论】:

        猜你喜欢
        • 2019-01-19
        • 2018-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多