做个有思想的程序员

做个有思想的程序员

对,你没看错,是让编译器写代码,编译器不仅是能编译代码,还能写代码。

废话少说,直接上代码,先看一个例子:

#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;
}

程序运行输出:

如何让编译器写代码,提高生产效率,避免996-LMLPHP

后来,我们又给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;后,程序运行结果如下:

如何让编译器写代码,提高生产效率,避免996-LMLPHP

这样,后面还给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;

程序运行输出:

如何让编译器写代码,提高生产效率,避免996-LMLPHP

在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;
}

04-22 02:13