1. 程式人生 > >如何讓編譯器寫程式碼,提高生產效率,避免996

如何讓編譯器寫程式碼,提高生產效率,避免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;