当我试图使用反复出现的模板模式实现静态多态性时,我注意到,通常在编译时检查某个类型是否可以转换为另一个类型的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为什么演员们工作时没有错误?如何进行编译时检查,以便从此类错误中获得帮助?
发布于 2018-01-10 17:24:58
在CRTP中解决这一问题的通常方法是使基类具有一个私有构造函数,并将模板中的类型声明为朋友:
template< typename T >
struct CRTP
{
void do_it( )
{
static_cast< T& >( *this ).execute( );
}
friend T;
private:
CRTP() {};
};在您的示例中,当您意外地让C从CRTP<A>继承时,由于C不是CRTP<A>的朋友,它不能调用它的构造函数,而且由于C必须构造它的所有基来构造自己,所以永远不能构造C。唯一的缺点是,这并不妨碍编译本身;要获得编译器错误,必须尝试实际构造C,或者为其编写用户定义的构造函数。在实践中,这仍然足够好,这样您就不必像其他解决方案所建议的那样在每个派生的派生中添加保护代码( IMHO违背了整个目的)。
生动的例子:http://coliru.stacked-crooked.com/a/38f50494a12dbb54。
注意:根据我的经验,CRTP的构造函数必须是“用户声明的”,这意味着您不能使用=default。否则,在这种情况下,您可以获得聚合初始化,这将不尊重private。同样,如果您想保持trivially_constructible特性(这并不是一个非常重要的特性),这可能是一个问题,但通常这并不重要。
发布于 2018-01-10 16:40:34
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我不建议使用建议的解决方案。我认为这是太多的开销,无法解释偶然的人为错误。
https://stackoverflow.com/questions/48192075
复制相似问题