對,你沒看錯,是讓編譯器寫代碼,編譯器不只是能編譯代碼,還能寫代碼。linux
廢話少說,直接上代碼,先看一個例子:ios
#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,該函數想打印輸出該結構體對象的各個字段,這個函數應該怎麼實現呢?blog
先定義一個模板類:遞歸
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; }