最近在网上看到了一个C++类型转换判断的高效实现,分享出来共同学习。作者使用了sizeof关键词、函数重载与可变参数的功能,功能实现得简洁优雅。Talk is cheap, show me the code~
#include <iostream>
using namespace std;
template<class T, class U>
class CHECK_TYPE{
class OTHER {char st[2];};
static char Test(U);
static OTHER Test(...);
static T USAGE();
public:
enum {result = sizeof(Test(USAGE())) == sizeof(char)};
};
class A {
};
class B : public A {
};
class C {
};
int main(){
cout << CHECK_TYPE<double, int>::result << endl;
cout << CHECK_TYPE<int, double>::result << endl;
cout << CHECK_TYPE<B, A>::result << endl;
cout << CHECK_TYPE<C, A>::result << endl;
}
分析:
- 使用类模板生成某两个具体类型转换判断的具体类实现并在初始化的时候完成类型能否进行转化的结果;
- 通过重载Test来表达出编译器对于类型的判断;
- 借助于sizeof关键字的特性,只定义了函数声明表达返回类型,无需具体的定义,判断时也无需执行函数;
- sizeof消耗最小,无函数调用,整个实现编译期已完成;而用typeinfo,typeid等需要解析,属于runtime。
代码中用到了可变参数。可变参数最典型的应用大概就是printf和scanf,原理是通过函数调用时候将类型、数目可变的参数全部压入函数调用栈,并定义了参数获取的宏来实现。以下面的函数定义为例,
void test(int count, ...)
{
... //do some stuff
}
由于函数参数压栈顺序是从右至左,所以在各个可变参数压入调用栈之后,count变量被压入相邻的低位栈地址(由于栈空间的使用由高位地址到低位地址增长)。使用传入的可变变量时,需要依赖count变量来初始化可变参数的初试地址。以上面函数的实现为例:
#include<stdarg.h>
//假设传入的可变参数都是整形变量,并且由count指示传入的可变参数的具体数量
void test(int count, ...)
{
va_list st;
va_start(st,count);
for(int i = 0; i < count; ++i)
{
cout<<va_arg(st, int)<<endl;
}
va_end(ap);
}
其中使用va_list类型表示可变参数,并用到了三个宏,分别是va_start、va_arg、va_end来实现可变参数的访问。va_list在stdarg.h头文件中被定义为char *类型
typedef char * va_list;
而三个宏的定义如下(这里只找到了win平台的实现,Linux平台的实现应该与之类似):
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_end(ap) ( ap = (va_list)0 )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
通过宏定义,不难看出
- va_start负责初始化可变参数的访问地址;
- va_end负责将指针置空;
- va_arg负责根据类型长度访问ap一段内存地址取出类型为t的数据,并将指针后移;
(1. ap += _INTSIZEOF(t):将ap指向下一个参数的地址)
(2. *(t *)(ap - _INTSIZEOF(t)):得到当前参数地址,并强制类型转换成指定类型) - _INTSIZEOF负责地址的对齐问题
最后,回到文章题目,关于类型转换判断的功能,C11中有stl::is_convertable的新feature,但是这个版本的实现显得简洁易懂。
感谢code作者 yunchenglu