简体   繁体   English

C ++代码生成

[英]C++ Code Generation

In my epic quest of making C++ do things it shouldn't, I am trying to put together a compile time generated class. 在我的史诗般的探索中,让C ++做的事情不应该,我试图把编译时生成的类放在一起。

Based on a preprocessor definition, such as (rough concept) 基于预处理器定义,例如(粗略概念)

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

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

While I am fairly sure I generate a class that reads the data from the file system using this sort of structure (Maybe even doing it using Template Metaprogramming), I don't see how I can generate both the functions to access the data and the function to read the data. 虽然我很确定我生成了一个使用这种结构从文件系统读取数据的类(甚至可能使用模板元编程实现),但我看不出如何生成访问数据的函数和功能来读取数据。

I would want to end up with a class something like this 我想最终得到类似这样的课程

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

Does anyone have any idea if this is even possible? 有没有人知道这是否可能?

--EDIT-- - 编辑 -

To clarify the intended usage for this. 澄清此用途的用途。 I have files in a standard format I want to read. 我有我想要阅读的标准格式的文件。 The format is defined already, so it is not open to change. 格式已经定义,因此无法更改。 Each file can contain any number records, each of which can contain any number sub records. 每个文件可以包含任何数字记录,每个记录可以包含任意数量的子记录。

The numerous record types each contain a diffrent set of sub records, but they can be are defined. 众多记录类型各自包含一组不同的子记录,但它们可以被定义。 So for example the Heightmap record must contain a Heightmap, but can optional contain normals. 因此,例如Heightmap记录必须包含Heightmap,但可选包含法线。

So I would want to define a Record for that like so: 所以我想像这样定义一个Record:

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

For which I would want to output something with the functionality of a class like this: 我想要输出具有类的功能的东西:

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

The issue I am having is that I need every preprocessor definition twice. 我遇到的问题是我需要两次预处理器定义。 Once for defining the function definition within the class, and once for creating the read function. 一次用于定义类中的函数定义,一次用于创建读取函数。 As the preprocessor is purely functional, I cannot push the data to a queue and generate the class on the END_CLASS marco definition. 由于预处理器纯粹是功能性的,我无法将数据推送到队列并在END_CLASS marco定义上生成类。

I cannot see a way around this issue, but wondered if anyone who has a greater understanding of C++ did. 我无法看到解决这个问题的方法,但想知道是否有人对C ++有更深入的了解。

If you are looking for a way to serialize/deserialize data with C++ code generation, I would look at Google protobufs ( http://code.google.com/p/protobuf/ ) or Facebook's Thrift ( http://incubator.apache.org/thrift/ ). 如果您正在寻找使用C ++代码生成序列化/反序列化数据的方法,我会查看Google protobufs( http://code.google.com/p/protobuf/ )或Facebook的Thrift( http://incubator.apache) .org / thrift / )。

For protobufs, you write a data definition like so: 对于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;
}

A Person C++ class is then generated that lets you load, save and access this data. 然后生成Person C ++类,允许您加载,保存和访问此数据。 You can also generate python, java, etc. 你也可以生成python,java等。

You might be able to solve this problem using boost tuples . 您可以使用boost 元组解决此问题。 It will result in a design which is different to what you are thinking of now, but it should allow you to solve the problem in a generic way. 它将导致设计与您现在想到的不同,但它应该允许您以通用方式解决问题。

The following example defines a record of the form "std::string,bool" and then reads that data in from a stream. 以下示例定义“std :: string,bool”形式的记录,然后从流中读取该数据。

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

using namespace ::boost::tuples;

The functions are used to read the data from an istream. 这些函数用于从istream中读取数据。 The first overload stops the iteration through the tuple after we reach the last record type: 在我们到达最后一个记录类型后,第一个重载会停止迭代通过元组:

//
// 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 ());
}

The following class implements the getter member for our Record. 以下类为Record实现getter成员。 Using the RecordKind as our key we get the specific member that we're interested in. 使用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);
  }
};

The next type is the meta description for our record. 下一个类型是我们记录的元描述。 The enumeration gives us a symbolic name that we can use to access the members, ie. 枚举为我们提供了一个可以用来访问成员的符号名称,即。 the field names. 字段名称。 The tuple then defines the types of those fields: 然后元组定义这些字段的类型:

struct HeightMap
{
  enum RecordKind
  {
    VHDT
    , VNML
  };

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

Finally, we construct a record and read in some data from a stream: 最后,我们构建一个记录并从流中读取一些数据:

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

And as this is all template code, you should be able to have records nested in records. 由于这是所有模板代码,您应该能够将记录嵌套在记录中。

This is a technique I use a lot in C and C++, called "list macro". 这是我在C和C ++中经常使用的一种技术,称为“列表宏”。 Suppose you have a list of things like variables, error messages, interpreter opcodes, or anything about which repetitive code needs to be written. 假设您有一些列表,包括变量,错误消息,解释器操作码或任何有关需要编写重复代码的内容。 In your case it is class member variables. 在你的情况下,它是类成员变量。

Suppose it is variables. 假设它是变量。 Put them in a list macro like this: 将它们放在列表宏中,如下所示:

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

To declare the variables, do this: 要声明变量,请执行以下操作:

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

Now you can generate any sort of repetitive code just by redefining DEFVAR and DEFARR, and instantiating MYVARS. 现在,您可以通过重新定义DEFVAR和DEFARR以及实例化MYVARS来生成任何类型的重复代码。

Some people find this rather jarring, but I think it's a perfectly good way to use the preprocessor as a code generator, and accomplish DRY. 有些人觉得这很刺耳,但我认为将预处理器用作代码生成器并完成DRY是一种非常好的方法。 And, the list macro itself becomes a mini-DSL. 而且,列表宏本身就变成了迷你DSL。

I might play around with a record mixin to do something similar -- add functionality to a class automagically at compile time 我可能会使用记录mixin来做类似的事情 - 在编译时自动向类添加功能

   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");

   }

In general you can accomplish exactly what you want if you merge everything into one macro and then leverage Booost Preprocessor library to define your class. 通常,如果将所有内容合并到一个宏中,然后利用Booost预处理器库来定义类,则可以完全按照您的要求完成。 Look at how I implemented the MACE_REFLECT macro which does a partial specialization of an entire class and must reference each name twice in different parts. 看看我是如何实现MACE_REFLECT宏的,它对整个类进行了部分特化,并且必须在不同的部分中引用每个名称两次。

This is very similar to how I automatically parse JSON into structs with the help of the pre-processor. 这与我在预处理器的帮助下自动将JSON解析为结构的方式非常相似。

Given your example, I would translate it as such: 举个例子,我会这样翻译:

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) )

I can now 'visit' the members of Name from my parser: 我现在可以从我的解析器“访问”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());

If your objects can be represented as structs, arrays, key-value-pairs and primitives, then this technique works wonders and gives me instant serialization/deserializtion to/from json/xml or your custom record format. 如果您的对象可以表示为结构,数组,键值对和基元,那么这种技术可以创建奇迹,并为我提供了对json / xml或自定义记录格式的即时序列化/反序列化。

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

I'm not exactly sure what you're looking for in some cases. 在某些情况下,我不确定你在寻找什么。

  • What happens to foo and bar in the specification? 规范中的foo和bar会发生什么?
  • What does getGroupName actually return? getGroupName实际返回什么? (foo,bar)? (FOO,吧)? or GroupName? 或GroupName?

It looks like you're trying to create a mechanism for loading and accessing on-disk structures of arbitrary layout. 看起来您正在尝试创建一种机制来加载和访问任意布局的磁盘结构。 Is this accurate? 这准确吗? (Edit: Just noticed the "set" member function... so I guess you're looking for full serialization) (编辑:刚注意到“设置”成员函数...所以我猜你正在寻找完整的序列化)

If you're on a *nix system, specifying your own compiler to compile to .o (likely a perl/python/what-have-you script that finishes with a call to gcc) in the Makefile is a trivial solution. 如果您使用的是* nix系统,那么在Makefile中指定您自己的编译器以编译为.o(可能是perl / python / what-have-you-you脚本,通过调用gcc完成)是一个简单的解决方案。 Others might know of ways of doing this on windows. 其他人可能知道在Windows上执行此操作的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM