我正在尝试使用奇怪的重复模板模式来实现静态多态性,当我注意到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
为什么演员表没有错误?如何进行编译时检查可以帮助解决此类错误?
答案 0 :(得分:8)
在CRTP中解决此问题的常用方法是使基类具有私有构造函数,并在模板中声明类型为朋友:
CRTP<A>
在您的示例中,当您意外地C
继承CRTP<A>
时,由于C
不是C
的朋友,因此无法调用其构造函数,并且因为C
必须构建其所有基础来构建自己,所以你永远不能构造=default
。唯一的缺点是,这并不妨碍汇编本身;要获得编译器错误,您必须尝试实际构造private
,或者为其编写用户定义的构造函数。在实践中,这仍然是足够好的,这样你就不必在每个派生中添加保护代码,正如其他解决方案所暗示的那样(恕我直言,这违背了整个目的)。
实例:http://coliru.stacked-crooked.com/a/38f50494a12dbb54。
注意:根据我的经验,CRTP的构造函数必须是“用户声明”,这意味着你不能使用trivially_constructible
。否则在这种情况下,您可以获得聚合初始化,这不会考虑.
。同样,如果你试图保持using System.Reflection;
using System.IO;
// ...
string exePath = Assembly.GetExecutingAssembly().Location;
string exeFolder = Path.GetDirectoryName(exePath);
string scriptPath = Path.Combine(exeFolder,"MyScript.ps1");
var command = new Command(". " + scriptPath);
特征(这不是一个非常重要的特征),这可能是一个问题,但通常它并不重要。
答案 1 :(得分: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 我不建议使用建议的解决方案。我认为考虑到偶然的人为错误会带来太多的开销。