C++ 中高效的 运行 时间类型检查
Efficient run-time type checking in C++
我有两个 class 命名为 'Expression' 和 'BinExp',代码如下:
class Expression
{
public:
virtual BinExp* IsBinaryExp() { return NULL; }
};
class BinExp : public Expression
{
public:
virtual BinExp* IsBinaryExp() { return this; }
};
例如,我有一个 Expression*
类型的指针变量,但初始化为 new BinExp
并作为参数发送给 analyse
函数,如下代码:
int main()
{
Expression* e = new BinExp;
analyse(e);
}
在analyse
函数中,我需要知道e
是指向Expression
类型还是BinExp
类型的指针。在我手上,有三种方法可以做到这一点。
第一个:
BinExp* be = e->IsBinaryExp();
if ( be )
{
printf("Yes, `e` is a binary expression\n");
}
第二个:
BinExp* be = dynamic_cast<BinExp*>(e);
if ( be )
{
printf("Yes, `e` is a binary expression\n");
}
第三个:
if ( typeid(*e) == typeid(BinExp) )
{
BinExp* be = e->IsBinaryExp(); // or dynamic_cast<BinExp*>(e);
printf("Yes, `e` is a binary expression\n");
}
但我想知道当我需要在性能很重要的循环中频繁执行检查时,这些方法中的哪一种(或任何其他方法)会更有效。我将不胜感激任何建议。
最快的方法是保留一个成员变量,比如一个枚举,然后在基 class 中定义一个内联 getter,然后您可以比较结果是否符合您的预期。
示例(未编译,可能会出现一些错误):
enum eExpTypes {
ET_UNDEFINED,
ET_BINARY
}
class Expresion
{
protected:
eExpTypes myType;
public:
Expresion(): myType(ET_UNDEFINED){};
inline eExpTypes getType(){return myType;};
}
class BinExpresion : public Expresion
{
public:
BinExpresion():myType(ET_BINARY){};
}
性能提升:
- 你将取出两个独立项:从指针到 vfptable,从 vfptable 到函数
- 如果类型函数是唯一的虚函数,你的 class 大小会更小
动态转换通常比创建您自己的类型检查机制慢,因此对于您的 3 个示例,第一个应该是最快的。
最快的是:
e->printIsBinExp();
你在哪里创建打印或者是 noop 的虚拟方法。
我只是在开玩笑。 virtual
方法的要点是封装不同类型对特定方法的处理 - 而不是编写程序来仅对不同运行时类型可能是什么进行运行时切换。
假设 dynamic_cast
最快。然后你会写:
if (BinExp* be = dynamic_cast<BinExp*>(e)) {
// ...
}
else if (UnExp* ue = dynamic_cast<UnExp*>(e)) {
// ...
}
else if (TernExp* te = dynamic_cast<TernExp*>(e)) {
// ...
}
希望不会。该代码将非常脆弱。您肯定会想出这样的设计:
e->eval();
作为单个 virtual
调用就做正确的事情。
3号最优雅
要找出是否最有效或哪个最有效,可以使用一些简单的代码来测量每种情况的执行时间...
#include <iostream>
#include <chrono>
/*
Case
*/
int main()
{
const clock_t begin_time = clock();
// Case
std::cout << float(clock() - begin_time) / CLOCKS_PER_SEC;
system("pause");
return 0;
}
我有两个 class 命名为 'Expression' 和 'BinExp',代码如下:
class Expression
{
public:
virtual BinExp* IsBinaryExp() { return NULL; }
};
class BinExp : public Expression
{
public:
virtual BinExp* IsBinaryExp() { return this; }
};
例如,我有一个 Expression*
类型的指针变量,但初始化为 new BinExp
并作为参数发送给 analyse
函数,如下代码:
int main()
{
Expression* e = new BinExp;
analyse(e);
}
在analyse
函数中,我需要知道e
是指向Expression
类型还是BinExp
类型的指针。在我手上,有三种方法可以做到这一点。
第一个:
BinExp* be = e->IsBinaryExp();
if ( be )
{
printf("Yes, `e` is a binary expression\n");
}
第二个:
BinExp* be = dynamic_cast<BinExp*>(e);
if ( be )
{
printf("Yes, `e` is a binary expression\n");
}
第三个:
if ( typeid(*e) == typeid(BinExp) )
{
BinExp* be = e->IsBinaryExp(); // or dynamic_cast<BinExp*>(e);
printf("Yes, `e` is a binary expression\n");
}
但我想知道当我需要在性能很重要的循环中频繁执行检查时,这些方法中的哪一种(或任何其他方法)会更有效。我将不胜感激任何建议。
最快的方法是保留一个成员变量,比如一个枚举,然后在基 class 中定义一个内联 getter,然后您可以比较结果是否符合您的预期。
示例(未编译,可能会出现一些错误):
enum eExpTypes {
ET_UNDEFINED,
ET_BINARY
}
class Expresion
{
protected:
eExpTypes myType;
public:
Expresion(): myType(ET_UNDEFINED){};
inline eExpTypes getType(){return myType;};
}
class BinExpresion : public Expresion
{
public:
BinExpresion():myType(ET_BINARY){};
}
性能提升:
- 你将取出两个独立项:从指针到 vfptable,从 vfptable 到函数
- 如果类型函数是唯一的虚函数,你的 class 大小会更小
动态转换通常比创建您自己的类型检查机制慢,因此对于您的 3 个示例,第一个应该是最快的。
最快的是:
e->printIsBinExp();
你在哪里创建打印或者是 noop 的虚拟方法。
我只是在开玩笑。 virtual
方法的要点是封装不同类型对特定方法的处理 - 而不是编写程序来仅对不同运行时类型可能是什么进行运行时切换。
假设 dynamic_cast
最快。然后你会写:
if (BinExp* be = dynamic_cast<BinExp*>(e)) {
// ...
}
else if (UnExp* ue = dynamic_cast<UnExp*>(e)) {
// ...
}
else if (TernExp* te = dynamic_cast<TernExp*>(e)) {
// ...
}
希望不会。该代码将非常脆弱。您肯定会想出这样的设计:
e->eval();
作为单个 virtual
调用就做正确的事情。
3号最优雅
要找出是否最有效或哪个最有效,可以使用一些简单的代码来测量每种情况的执行时间...
#include <iostream>
#include <chrono>
/*
Case
*/
int main()
{
const clock_t begin_time = clock();
// Case
std::cout << float(clock() - begin_time) / CLOCKS_PER_SEC;
system("pause");
return 0;
}