代码之家  ›  专栏  ›  技术社区  ›  Yacoby

C++代码生成

  •  10
  • Yacoby  · 技术社区  · 15 年前

    在我的史诗般的追求,使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
        }
    };
    

    有人知道这是否可能吗?

    --编辑——

    以明确其预期用途。我有标准格式的文件要读。格式已定义,因此无法更改。每个文件可以包含任何数字记录,每个记录都可以包含任何数字子记录。

    许多记录类型都包含一组不同的子记录,但可以定义它们。例如,heightmap记录必须包含heightmap,但可以选择包含normals。

    所以我想定义一个这样的记录:

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

    我现在遇到的问题是,我需要每个预处理器定义两次。一次用于定义类中的函数定义,一次用于创建读取函数。由于预处理器是纯功能的,所以我不能将数据推送到队列中,并在结束类marco定义上生成类。

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

    6 回复  |  直到 15 年前
        1
  •  5
  •   Richard Corden    15 年前

    你也许可以用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 ());
    }
    

    下面的类实现了记录的getter成员。使用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);
      }
    };
    

    下一个类型是记录的元描述。枚举给了我们一个可以用来访问成员的符号名,即字段名。然后,元组定义这些字段的类型:

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

    因为这都是模板代码,所以您应该能够将记录嵌套在记录中。

        2
  •  8
  •   Joe Beda    15 年前

    如果您正在寻找一种用C++代码生成来串行化/反序列化数据的方法,那么我将查看谷歌原型(FixBuffs)。 http://code.google.com/p/protobuf/ )或者Facebook的节俭( 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++类,让您加载、保存和访问这些数据。还可以生成Python、Java等。

        3
  •  3
  •   Mike Dunlavey    15 年前

    这是我在C和C++中使用的一种技术,被称为“列表宏”。假设您有一个变量、错误消息、解释器操作码或任何需要编写重复代码的东西的列表。在您的例子中,它是类成员变量。

    假设它是变量。将它们放入这样的列表宏中:

    #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,并实例化myvar,就可以生成任何类型的重复代码。

    有些人觉得这有点刺耳,但我认为这是将预处理器用作代码生成器并完成Dry的一个非常好的方法。而且,list宏本身就变成了一个迷你DSL。

        4
  •  2
  •   Doug T.    15 年前

    我可能会用一个记录混合来做类似的事情——在编译时自动地向类添加功能。

       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");
    
       }
    
        5
  •  1
  •   bytemaster    12 年前

    一般来说,如果将所有内容合并到一个宏中,然后利用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) )
    

    我现在可以从解析器“访问”name的成员:

    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

        6
  •  0
  •   jkerian    15 年前

    我不太清楚你在找什么。

    • 规范中的foo和bar发生了什么?
    • GetGroupName实际返回了什么?(酒吧,酒吧)?或组名?

    看起来您正试图创建一种机制来加载和访问任意布局的磁盘结构。这是准确的吗?(编辑:刚刚注意到“set”成员函数…所以我猜你在找完整的系列)

    如果您使用的是*nix系统,那么在makefile中指定自己的编译器编译为.o(很可能是perl/python/您的脚本以调用gcc结束)是一个简单的解决方案。其他人可能知道在Windows上执行此操作的方法。