对,你没看错,是让编译器写代码,编译器不仅是能编译代码,还能写代码。
废话少说,直接上代码,先看一个例子:
#include <string>
#include<iostream>
struct stObject
{
char one;
int tow;
float three;
std::string four;
};
template<typename T>
void Print( T & obj)
{
//想在此,打印输出obj对象的各个字段的值,怎么做?
}
int main()
{
stObject obj;
Print(obj);
printf("Enter any key for exit!");
getchar();
return 0;
}
有一个结构体stObject,以及一个模板函数Print,该函数想打印输出该结构体对象的各个字段,这个函数应该怎么实现呢?
先定义一个模板类:
template <int size>
struct Int2Type
{
enum { Size = size };
};
这个模板类的类型参数是int, 当这个整数值不同时,就是不同的类型,例如 Int2Type<1> ,和Int2Type<2>,Int2Type<3>等等,都不同的类型。
接着我们给 stObject结构体,增加几个成员函数,如下:
struct stObject
{
char one;
int tow;
float three;
std::string four;
////增加成员函数获取字段值的引用
auto get(Int2Type<1>) -> decltype((one)) { return one;}
auto get(Int2Type<2>) -> decltype((tow)) { return tow; }
auto get(Int2Type<3>) -> decltype((three)) { return three; }
auto get(Int2Type<4>) -> decltype((four)) { return four; }
};
于时我们的Print模板函数就可以这样实现了:
template<typename T>
void Print( T & obj)
{
//想在此,打印输出obj对象的各个字段的值,怎么做?
std::cout << obj.get(Int2Type<1>()) << ","
<< obj.get(Int2Type<2>()) << ","
<< obj.get(Int2Type<3>()) << ","
<< obj.get(Int2Type<4>()) << std::endl;
}
然后在我们的main函数中,给ojb对象的字段赋一些值,如下:
int main()
{
stObject obj;
obj.one = 100; //赋值
obj.tow = 2;
obj.three = 3;
obj.four = "4";
Print(obj);
printf("Enter any key for exit!");
getchar();
return 0;
}
程序运行输出:
后来,我们又给stObject增加了一个字段 : short five;
程序运行后Print模板函数,只能输出前四个字段,第五个没有输出。这样Print函数也需要跟着改动,这样太烦人了,有没有更好的办法呢。办法是让Print函数知道obj对像共有几个字段。我们给stObject结构体增加一个枚举,这个枚举值指定本结构体共有几个字段,同时修改Print函数:
struct stObject
{
enum {Size = 5}; //指明本结构体有5个字段
char one;
int tow;
float three;
std::string four;
short five; //新增加的字段
//增加成员函数获取字段值的引用
auto get(Int2Type<1>) -> decltype((one)) { return one;}
auto get(Int2Type<2>) -> decltype((tow)) { return tow; }
auto get(Int2Type<3>) -> decltype((three)) { return three; }
auto get(Int2Type<4>) -> decltype((four)) { return four; }
auto get(Int2Type<5>) -> decltype((five)) { return five; } //新增加的函数
};
template<typename T>
void Print( T & obj)
{
//想在此,打印输出obj对象的各个字段的值,怎么做?
Print_i(obj,Int2Type<1>()); //先输出第一个字段
}
template<typename T, typename int size>
void Print_i(T & obj, Int2Type<size> index)
{
//想在此,打印输出obj对象的各个字段的值,怎么做?
std::cout << obj.get(index) << ",";
Print_i(obj,Int2Type<size + 1>()); //递归输出下一个字段
}
template<typename T>
void Print_i(T & obj, Int2Type<obj.Size+1>) //递归结束
{
std::cout << std::endl;
}
在main函数中给obj.five = 101;后,程序运行结果如下:
这样,后面还给stObject增加字段,Print函数都不需要修改了。只需要修改stObject的枚举值,以及增加要应的get成员函数获取字段值。这样还是显得有些烦锁。我们进一步优化。
先定义几个宏:
#define FIELD_BEGIN() enum{Begin = __COUNTER__};
#define FIELD_END() enum{Size = __COUNTER__ - Begin -1 };
#define FIELD(type,name) FIELD_INDEX(type,name,(__COUNTER__- Begin))
#define FIELD_INDEX(type,name,index) DEFINE_FILED(type,name,index)
#define DEFINE_FILED(type,name,index) type name; auto get(Int2Type<index>) -> decltype((name)) { return name;}
同时把结构体stObject修改如下:
struct stObject
{
/*
enum {Size = 5};
char one;
int tow;
float three;
std::string four;
short five; //新增加的字段
//增加成员函数获取字段值的引用
auto get(Int2Type<1>) -> decltype((one)) { return one;}
auto get(Int2Type<2>) -> decltype((tow)) { return tow; }
auto get(Int2Type<3>) -> decltype((three)) { return three; }
auto get(Int2Type<4>) -> decltype((four)) { return four; }
auto get(Int2Type<5>) -> decltype((five)) { return five; } //新增加的函数
*/
FIELD_BEGIN()
FIELD(char, one)
FIELD(int, tow)
FIELD(float, three)
FIELD(std::string, four)
FIELD(short, five)
FIELD_END()
};
先看宏FIELD_BEGIN(),该宏不带参数,它后面跟着的代码是:enum{Begin = __COUNTER__}; 把FIELD_BEGIN()放在stObject结构体的定义中,当编译器把宏展开之后,下面的两段代码是相同的:
struct stObject
{
FIELD_BEGIN()
}
等同于:
struct stObject
{
enum{Begin = __COUNTER__}; //即定义了一个枚举,
}
而宏__COUNTER__是编译器内置的宏,编译器第一次遇到它时,用0来替换该宏,第二次遇到它时,用1来替换,依次类推。
再看第二个宏FIELD_END()该宏也不带参数,后面跟的代码时enum{Size = __COUNTER__ - Begin -1 };,也是定义了一个枚举。
那么
struct stObject
{
FIELD_BEGIN() //假设__COUNTER__的值为n
FIELD_END() //这里__COUNTER__的值为n+1,那么枚举Size的值为n+1 - n -1 =0,代表这个结构体有0个成员字段。
}
再看宏#define FIELD(type,name) FIELD_INDEX(type,name,(__COUNTER__- Begin)),该宏带有两个参数,第一个参数代表 结构体要定义的字段类型,第二个参数,代表结构体要定义的字段名字,该宏调用了下面的宏:
#define FIELD_INDEX(type,name,index) DEFINE_FILED(type,name,index)
参数type,name的意义和宏FIELD一样,而第三个参数index代表这是宏的第几个字段。该宏又调用了下面的宏:
#define DEFINE_FILED(type,name,index) type name; auto get(Int2Type<index>) -> decltype((name)) { return name;}
该宏的参数和FIELD_INDEX一样,后面跟的代码 type name; 表示给结构体定义一个字段,类型为type, 字段名为name, 后面还跟了一个get成员函数获取该字段的值。
所以下面的结构体定义,宏展开后,和/**/中的代码是等同的:
struct stObject
{
FIELD_BEGIN()
FIELD(char, one)
FIELD(int, tow)
FIELD(float, three)
FIELD(std::string, four)
FIELD(short, five)
FIELD_END()
/*宏展开后,同等于下面的代码:
enum {Begin = __COUNTER__}
char one;
int tow;
float three;
std::string four;
short five; //新增加的字段
//增加成员函数获取字段值的引用
auto get(Int2Type<1>) -> decltype((one)) { return one;}
auto get(Int2Type<2>) -> decltype((tow)) { return tow; }
auto get(Int2Type<3>) -> decltype((three)) { return three; }
auto get(Int2Type<4>) -> decltype((four)) { return four; }
auto get(Int2Type<5>) -> decltype((five)) { return five; } //新增加的函数
enum {Size = 5};
*/
};
需要注意的是每个FIELD宏需要单独占一行,否则__COUNTER__计算会错乱。
后面需要给结构体增加新字段时,只需要增加一行FIELD(),例如 FIELD(long , six)
当结构体的字段比较多时,Print函数,只输出字段值,没有什么意义,假如能连字段名也输出就好了。说干就干。
先定义一个新宏:
#define DEFINE_NAME_FUNC(name,index) const char* get_name(Int2Type<index>){return #name;}
这个宏带两个参数,一个字段名,一个是字段索引(即代表是第几个字段),宏的代码是定义一个成员函数,获取字段名,然后修改宏FIELD_INDEX:
#define FIELD_INDEX(type,name,index) DEFINE_FILED(type,name,index) DEFINE_NAME_FUNC(name,index)
这样就成功给结构体的每个字段增加一个获取字段名的成员函数。再把Print_i函数修改如下:
template<typename T, typename int size>
void Print_i(T & obj, Int2Type<size> index)
{
//想在此,打印输出obj对象的各个字段的值,怎么做?
std::cout << obj.get_name(index) << ":" << obj.get(index) << ","; //先输出结构体字段的名字
Print_i(obj,Int2Type<size + 1>()); //递归输出下一个字段
}
最后,给结构体stOjbect增加两个字段:
FIELD(long, six)
FIELD(long, seven)
然后在main函数中赋值
obj.six = obj.Begin;
obj.seven = obj.Size;
程序运行输出:
在linux中调试程序就很方便啦,一条语句就可以把结构体打印输出,增加字段也不需要修改Print函数。使用相同的方法,很容易,让一个结构体和.ini文件绑定,一条语句就把整个.ini的字段读到结构体中。还有在数据库方面的应用,一个结构体和一个数据库表绑定。一条语名就可以把数据库表读到结构体vector中。大大的增加开发效率,见过很多操作数据库的代码,不停的重复着一个一个字段的绑定输入参数,然后查询数据库,然后获取查询结果集,然后一个一个字段给结构体赋值。这样的代码是丑陋无比的,也容易出错,这样的脏活累活交给编译器写代码完成就啦。而且模板都是在编译值求值,而且是内联函数。所以性能也扛扛的。再也不用996。最后附上该例子的完整代码:
#include <string>
#include<iostream>
#define FIELD_BEGIN() enum{Begin = __COUNTER__};
#define FIELD(type,name) FIELD_INDEX(type,name,(__COUNTER__- Begin))
#define FIELD_END() enum{Size = __COUNTER__ - Begin -1 };
#define FIELD_INDEX(type,name,index) DEFINE_FILED(type,name,index) DEFINE_NAME_FUNC(name,index)
#define DEFINE_FILED(type,name,index) type name; auto get(Int2Type<index>) -> decltype((name)) { return name;}
#define DEFINE_NAME_FUNC(name,index) const char* get_name(Int2Type<index>){return #name;}
template <int size>
struct Int2Type
{
enum { Size = size };
};
struct stObject
{
FIELD_BEGIN()
FIELD(char, one)
FIELD(int, tow)
FIELD(float, three)
FIELD(std::string, four)
FIELD(short, five)
FIELD(long, six)
FIELD(long, seven)
FIELD_END()
/*宏展开后,同等于下面的代码:
enum {Begin = __COUNTER__}
char one;
int tow;
float three;
std::string four;
short five; //新增加的字段
long six;
long seven;
//增加成员函数获取字段值的引用
auto get(Int2Type<1>) -> decltype((one)) { return one;}
auto get(Int2Type<2>) -> decltype((tow)) { return tow; }
auto get(Int2Type<3>) -> decltype((three)) { return three; }
auto get(Int2Type<4>) -> decltype((four)) { return four; }
auto get(Int2Type<5>) -> decltype((five)) { return five; } //新增加的函数
auto get(Int2Type<6>) -> decltype((six)) { return six; } //新增加的函数
auto get(Int2Type<7>) -> decltype((seven)) { return seven; } //新增加的函数
enum {Size = 7};
*/
};
template<typename T>
void Print( T & obj)
{
//想在此,打印输出obj对象的各个字段的值,怎么做?
Print_i(obj,Int2Type<1>()); //先输出第一个字段
}
template<typename T, typename int size>
void Print_i(T & obj, Int2Type<size> index)
{
//想在此,打印输出obj对象的各个字段的值,怎么做?
std::cout << obj.get_name(index) << ":" << obj.get(index) << ","; //先输出结构体名字
Print_i(obj,Int2Type<size + 1>()); //递归输出下一个字段
}
template<typename T>
void Print_i(T & obj, Int2Type<obj.Size+1>) //递归结束
{
std::cout << std::endl;
}
int main()
{
stObject obj;
obj.one = 100;
obj.tow = 2;
obj.three = 3;
obj.four = "4";
obj.five = 101;
obj.six = obj.Begin;
obj.seven = obj.Size;
Print(obj);
printf("Enter any key for exit!");
getchar();
return 0;
}