C++代码生成

问题描述:

在我的史诗追求使C++做的事情不应该,我试图把编译时间生成的类放在一起。C++代码生成

基于预处理器的定义,如(粗略的概念)

CLASS_BEGIN(Name) 
    RECORD(xyz) 
    RECORD(abc) 

    RECORD_GROUP(GroupName) 
     RECORD_GROUP_RECORD(foo) 
     RECORD_GROUP_RECORD(bar) 
    END_RECORDGROUP 
END_CLASS 

虽然我相当肯定,我产生一个类,使用这种结构的文件系统读取数据(甚至做使用模板元编程),我没有看到如何生成访问数据的函数和读取数据的函数。

我希望有一类这样的事情结束了

class Name{ 
    public: 
    xyz_type getxyz(); 
    void setxyz(xyz_type v); 

    //etc 

    list<group_type> getGroupName(); 

    //etc 

    void readData(filesystem){ 
     //read xyz 
     //read abc 
     //etc 
    } 
}; 

没有人有任何想法,如果这甚至有可能?

- EDIT--

明确此目的的用法。我有我想阅读的标准格式的文件。该格式已经定义,所以不会改变。每个文件可以包含任何数量的记录,每个记录可以包含任何数量的子记录。

许多记录类型每个都包含一组不同的子记录,但它们可以被定义。因此,例如,高度图记录必须包含高度图,但可以包含法线。

所以我想定义一个纪录,像这样:

CLASS_BEGIN(Heightmap) 
    RECORD(VHDT, Heightmap, std::string) //Subrecord Name, Readable Name, Type 
    RECORD_OPTIONAL(VNML, Normals, std::string) 
END_CLASS 

因我所希望输出一些与一类这样的功能:

class Heightmap{ 
    public: 
    std::string getHeightmap(){ 
     return mHeightmap->get<std::string>(); 
    } 
    void setHeightmap(std::string v){ 
     mHeight->set<std::string>(v); 
    } 

    bool hasNormal(){ 
     return mNormal != 0; 
    } 
    //getter and setter functions for normals go here 

    private: 
    void read(Record* r){ 
     mHeightmap = r->getFirst(VHDT); 
     mNormal = r->getFirst(VNML); 
    } 


    SubRecord* mHeightmap, mNormal; 
} 

问题我遇到的情况是我需要两次预处理器定义。一次用于在类中定义函数定义,一次用于创建读取函数。由于预处理器纯粹是功能性的,我无法将数据推送到队列并在END_CLASS marco定义中生成类。

我看不出解决这个问题的方法,但想知道是否有人对C++有更深入的理解。

+0

这是可能的,我怀疑。你能更精确地知道你到底在哪里? – 2009-06-05 16:51:52

+0

这么做并不明显,你可以从中得到什么好处 - 请解释一下。 – 2009-06-05 16:56:54

您可能可以使用boost tuples解决此问题。它会产生一种与你现在想的不同的设计,但它应该允许你以通用的方式解决问题。

以下示例定义窗体“std :: string,bool”的记录,然后从流中读取该数据。

#include "boost/tuple/tuple.hpp" 
#include <iostream> 
#include <sstream> 

using namespace ::boost::tuples; 

该函数用于读取istream中的数据。第一个重载通过元组停止迭代后,我们到达最后一个记录类型:

// 
// This is needed to stop when we have no more fields 
void read_tuple (std::istream & is, boost::tuples::null_type) 
{ 
} 

template <typename TupleType> 
void read_tuple (std::istream & is, TupleType & tuple) 
{ 
    is >> tuple.template get_head(); 
    read_tuple (is, tuple.template get_tail()); 
} 

下面的类实现了我们的录制气构件。使用RecordKind作为我们的重点,我们得到我们感兴趣的特定成员。

template <typename TupleType> 
class Record 
{ 
private: 
    TupleType m_tuple; 

public: 
    // 
    // For a given member - get the value 
    template <unsigned int MBR> 
    typename element <MBR, TupleType>::type & getMember() 
    { 
    return m_tuple.template get<MBR>(); 
    } 

    friend std::istream & operator>> (std::istream & is 
            , Record<TupleType> & record) 
    { 
    read_tuple (is, record.m_tuple); 
    } 
}; 

下一个类型是我们记录的meta描述。枚举为我们提供了一个可以用来访问成员的象征性名称,即。字段名称。元组则定义类型的字段:

struct HeightMap 
{ 
    enum RecordKind 
    { 
    VHDT 
    , VNML 
    }; 

    typedef boost::tuple < std::string 
         , bool 
        > TupleType; 
}; 

最后,我们构建了一个纪录,从数据流中的一些数据读取:

int main() 
{ 
    Record<HeightMap::TupleType> heightMap; 
    std::istringstream iss ("Hello 1"); 

    iss >> heightMap; 

    std::string s = heightMap.getMember <HeightMap::VHDT>(); 
    std::cout << "Value of s: " << s << std::endl; 


    bool b = heightMap.getMember <HeightMap::VNML>(); 
    std::cout << "Value of b: " << b << std::endl; 
}  

而且,因为这是所有的模板代码,你应该能够将记录嵌套在记录中。

如果您正在寻找一种使用C++代码生成序列化/反序列化数据的方法,我会查看Google protobufs(http://code.google.com/p/protobuf/)或Facebook的Thrift(http://incubator.apache.org/thrift/)。

对于protobufs,则写数据定义像这样:然后

message Person { 
    required string name = 1; 
    required int32 id = 2; 
    optional string email = 3; 

    enum PhoneType { 
    MOBILE = 0; 
    HOME = 1; 
    WORK = 2; 
    } 

    message PhoneNumber { 
    required string number = 1; 
    optional PhoneType type = 2 [default = HOME]; 
    } 

    repeated PhoneNumber phone = 4; 
} 

一个人C++类产生,让您加载,保存和访问这些数据。您还可以生成蟒蛇,Java等

我会玩的一个记录混入做同样的事情 - 一类自动地在编译时

template<class Base, class XyzRecType> 
    class CRecord : public Base 
    { 
    protected: 
     RecType xyz; 
    public: 
     CRecord() : Base() {} 


     RecType Get() {return xyz;} 

     void Set(const RecType& anXyz) {xyz = anXyz;} 

     void ReadFromStream(std::istream& input) 
     { 
      ... 
     } 

    }; 

    class CMyClass 
    { 
    }; 

    int main() 
    { 
     // now thanks to the magic of inheritance, my class has added methods! 
     CRecord<CMyClass, std::string> myClassWithAStringRecord; 

     myClassWithAStringRecord.Set("Hello"); 

    } 

添加功能,我不太确定在某些情况下你正在寻找什么。

  • 规格中的foo和bar会发生什么变化?
  • getGroupName实际返回什么? (FOO,吧)?或GroupName?

它看起来像你试图创建一个机制来加载和访问任意布局的磁盘结构。这是否准确? (编辑:只注意到“设置”成员函数...所以我想你正在寻找完整的序列化)

如果你在* nix系统,指定你自己的编译器编译为.o(可能一个perl/python/what-have-you脚本完成了对gcc的调用)在Makefile中是一个简单的解决方案。其他人可能知道在Windows上做这件事的方法。

这是我在C和C++中大量使用的一种技术,名为“list macro”。假设你有一系列变量,错误消息,解释器操作码或任何需要写入重复代码的东西。在你的情况下,它是类成员变量。

假设它是变量。把它们放在一个列表宏是这样的:

#define MYVARS \ 
DEFVAR(int, a, 6) \ 
DEFVAR(double, b, 37.3) \ 
DEFARR(char, cc, 512) \ 

要声明的变量,这样做:

#define DEFVAR(typ,nam,inival) typ nam = inival; 
#define DEFARR(typ,nam,len) typ nam[len]; 
    MYVARS 
#undef DEFVAR 
#undef DEFARR 

现在,你可以通过重新定义DEFVAR和DEFARR,以及实例MYVARS产生任何形式的重复代码。

有些人觉得这很刺激,但我认为这是一种使用预处理器作为代码生成器并完成DRY的完美方法。而且,这个列表宏本身就变成了一个mini-DSL。

+0

在C中,是的。通常在C++中,我会首先查找TMP解决方案。这可能有助于解释为什么我在C中更高效。 – 2010-01-14 14:19:28

一般而言,如果将所有内容合并到一个宏中,然后利用Booost预处理程序库来定义您的类,则可以完全实现您想要的功能。看看我是如何实现MACE_REFLECT宏的,它完成了整个类的部分特化,并且必须在不同部分中引用两次每个名称。

这与我如何在预处理器的帮助下自动将JSON解析到结构中非常相似。

鉴于你的榜样,我将它翻译为这样:

struct Name { 
    xyz_type xyz; 
    abc_type abc; 
    boost::optional<foo_type> foo; 
    boost::optional<bar_type> bar; 
}; 
MACE_REFLECT(Name, (xyz)(abc)(foo)(bar)) 

我现在可以“访问”名称来自我的解析器成员:

struct visitor { 
    template<typename T, T p> 
    inline void operator()(const char* name)const { 
     std::cout << name << " = " << c.*p; 
    } 
    Name c; 
}; 
mace::reflect::reflector<Name>::visit(visitor()); 

如果你的对象可以表示为结构体,数组,键 - 值对和原语,那么这种技术可以创造奇迹,并使我能够从json/xml或自定义记录格式中立即序列化/反序列化。

https://github.com/bytemaster/mace/blob/master/libs/rpc/examples/jsonv.cpp