如何讓編譯器寫程式碼,提高生產效率,避免996
對,你沒看錯,是讓編譯器寫程式碼,編譯器不僅是能編譯程式碼,還能寫程式碼。
廢話少說,直接上程式碼,先看一個例子:
#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;