9

我试图使用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

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

4

2 回答 2

10

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

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

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

现场示例:http ://coliru.stacked-crooked.com/a/38f50494a12dbb54 。

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

于 2018-01-10T17:24:58.203 回答
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我不建议使用建议的解决方案。我认为考虑偶尔的人为错误的开销太大。

于 2018-01-10T16:40:34.520 回答