【问题标题】:Has CRTP no compile time check?CRTP 没有编译时检查吗?
【发布时间】:2018-06-19 21:33:10
【问题描述】:

我试图使用 Curiously Recurring Template Pattern 实现静态多态性,但我注意到 static_cast<> 通常在编译时检查一个类型是否真的可以转换为另一个类型,但它漏掉了一个错字在基类声明中,允许代码将基类向下转换为它的同级之一:

#include <iostream>

using namespace std;

template< typename T >
struct CRTP
{
    void do_it( )
    {
        static_cast< T& >( *this ).execute( );
    }
};

struct A : CRTP< A >
{
    void execute( )
    {
        cout << "A" << endl;
    }
};

struct B : CRTP< B >
{
    void execute( )
    {
        cout << "B" << endl;

    }
};

struct C : CRTP< A > // it should be CRTP< C >, but typo mistake
{
    void execute( )
    {
        cout << "C" << endl;
    }
};

int main( )
{
    A a;
    a.do_it( );
    B b;
    b.do_it( );
    C c;
    c.do_it( );
    return 0;
}

程序的输出是:

A
B
A

为什么演员表工作没有错误?如何进行编译时检查以帮助解决此类错误?

【问题讨论】:

  • 您可以static_cast 到派生类。您无法在编译时检查运行时类型。
  • @MooingDuck,这很好,但它不会像问题中那样传递错误的派生类型。但要抓住这一点,我认为无论如何都必须在语言中支持 CRTP(例如,mixins)。
  • 哦,哎呀。你说得对。出于某种原因,我认为其中一个是 C,但它们都是 A。也许如果一个变体进入 derived 类? static_assert(std::is_base_of&lt;CRTP&lt;decltype(this)&gt;,decltype(this)&gt;::value)?
  • @MooingDuck,是的,我认为这是你今天能得到的最好的。使用元类,我相信您至少可以拥有某种 crtp&lt;CRTP&gt; C { ... }; 并且根本不需要手动继承。也就是说,我认为元类更适合将所需的 mixins 组合成一个新的元类或一些通用的with_mixins&lt;...&gt; Foo { ... };
  • AC 都派生自CRTP&lt;A&gt;,因此static_cast 允许从CRTP&lt;A&gt; 到其中任何一个。转换为不相关(或模棱两可)的类型,例如不过,B 需要进行诊断。

标签: c++ static-cast class-template static-polymorphism


【解决方案1】:

在 CRTP 中解决这个问题的通常方法是让基类有一个私有构造函数,并在模板中声明类型为友元:

template< typename T >
struct CRTP
{
    void do_it( )
    {
        static_cast< T& >( *this ).execute( );
    }
    friend T;
private:
    CRTP() {};
};

在你的例子中,当你不小心让CCRTP&lt;A&gt;继承时,由于C不是CRTP&lt;A&gt;的朋友,它不能调用它的构造函数,并且由于C必须构造所有它的基础来构建自己,你永远不能构建一个C。唯一的缺点是这并不能阻止编译本身。要获得编译器错误,您要么必须尝试实际构造C,要么为其编写用户定义的构造函数。在实践中,这仍然足够好,这样您就不必像其他解决方案所建议的那样在每个派生中添加保护性代码(恕我直言,这违背了整个目的)。

实时示例:http://coliru.stacked-crooked.com/a/38f50494a12dbb54

注意:根据我的经验,CRTP 的构造函数必须是“用户声明的”,这意味着您不能使用=default。否则在这种情况下,您可以获得聚合初始化,这将不尊重private。同样,如果您试图保留 trivially_constructible 特征(这不是一个非常重要的特征),这可能是一个问题,但通常它应该无关紧要。

【讨论】:

  • 这难道不允许一个人做像void MyBadClass::memberFunction() {CRTP&lt;MyBadClass&gt; x; x.do_it();}这样的傻事吗?
  • @JoshuaGreen 好吧,构造函数在原始解决方案中是公共的,所以如果您想独立构建 CRTP 类,没有什么能阻止您。所以,我不会说它“允许”你做一些与原始情况相关的傻事。
  • 这很公平。我以为我有办法解决这个问题,但现在我很难记住它。
  • @Joshua Green 你可以给它一个抽象方法。那么它不能单独实例化,只能作为基础。但是,您可能正在添加一个 vtable 和样板文件。防范墨菲,而不是马基雅维利。
  • @Fan 是的,正确使用模式:Abstract&lt;T&gt; : CRTP&lt;Abstract&lt;T&gt;&gt;。也就是说,没有理由在 C++ 静态多态中使用“抽象”类。
【解决方案2】:

Q1为什么演员可以正常工作?

当没有任何明智的事情适用时......

来自https://timsong-cpp.github.io/cppwp/n3337/expr.static.cast#2

否则,强制转换的结果是未定义的。


Q2如何进行编译时检查以帮助解决此类错误?

我找不到可以在CRTP 中使用的方法。我能想到的最好的办法是在派生类中添加static_assert

例如,如果您将C 更改为:

struct C : CRTP< A > // it should be CRTP< C >, but typo mistake
{
   static_assert(std::is_base_of<CRTP<C>, C>::value, "");
   void execute( )
   {
      cout << "C" << endl;
   }
};

你会在编译时看到错误。

你可以把它简化为

struct C : CRTP< A > // it should be CRTP< C >, but typo mistake
{
   using ThisType = C;
   static_assert(std::is_base_of<CRTP<ThisType>, ThisType>::value, "");
   void execute( )
   {
      cout << "C" << endl;
   }
};

需要在每个派生类型中添加类似的代码。它并不优雅,但它会起作用。

PS我不建议使用建议的解决方案。我认为考虑偶尔出现的人为错误的开销太大。

【讨论】:

  • "我如何进行编译时检查以帮助解决此类错误?"确实是问题的关键部分
  • @MooingDuck,我从您的评论中复制了解决方案。希望你不要介意。
  • 我不介意,但chris 观察到it's wrong
  • 我应该在哪里写static_assert?我将它添加为 do_it() 方法的第一行,但尽管有错字,它仍然可以编译。
  • @nyarlathotep108:在派生类中尝试static_assert(std::is_base_of&lt;CRTP&lt;decltype(this)&gt;,decltype(‌​this)&gt;::value)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-14
  • 2020-05-25
  • 1970-01-01
  • 1970-01-01
  • 2014-02-08
相关资源
最近更新 更多