Генерация кода C ++ - PullRequest
       13

Генерация кода C ++

10 голосов
/ 05 июня 2009

В моем эпическом стремлении заставить 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
    }
};

Кто-нибудь знает, если это вообще возможно?

- РЕДАКТИРОВАТЬ -

Чтобы уточнить предполагаемое использование для этого. У меня есть файлы в стандартном формате, который я хочу прочитать. Формат уже определен, поэтому он не подлежит изменению. Каждый файл может содержать любое количество записей, каждая из которых может содержать любое количество вложенных записей.

Каждый из многочисленных типов записей содержит различный набор вложенных записей, но они могут быть определены. Так, например, запись карты высот должна содержать карту высот, но необязательно может содержать нормали.

Итак, я бы хотел определить запись для этого следующим образом:

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 ++, сделал это.

Ответы [ 6 ]

8 голосов
/ 05 июня 2009

Если вы ищете способ сериализации / десериализации данных с помощью генерации кода 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;
}

Затем создается класс Person C ++, который позволяет загружать, сохранять и получать доступ к этим данным. Вы также можете генерировать Python, Java и т. Д.

5 голосов
/ 08 июня 2009

Вы можете решить эту проблему, используя boost tuples . Это приведет к дизайну, который отличается от того, о чем вы думаете сейчас, но он должен позволить вам решить проблему в общем виде.

В следующем примере определяется запись в форме "std :: string, bool", а затем считываются эти данные из потока.

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

using namespace ::boost::tuples;

Функции используются для чтения данных из потока. Первая перегрузка останавливает итерацию через кортеж после достижения последнего типа записи:

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

Следующий тип - это мета-описание нашей записи. Перечисление дает нам символическое имя, которое мы можем использовать для доступа к членам, т.е. имена полей. Затем кортеж определяет типы этих полей:

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

И так как это весь код шаблона, вы должны иметь возможность иметь записи, вложенные в записи.

3 голосов
/ 10 июня 2009

Это техника, которую я часто использую в 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 и создав экземпляр MYVARS.

Некоторые люди считают это довольно резким, но я думаю, что это очень хороший способ использовать препроцессор в качестве генератора кода и выполнить DRY. И сам макрос списка становится мини-DSL.

2 голосов
/ 05 июня 2009

Я мог бы поиграться с рекордным миксином, чтобы сделать что-то похожее - добавить функциональность в класс автоматически во время компиляции

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

   }
1 голос
/ 29 июля 2012

В общем, вы можете достичь именно того, что вы хотите, если вы объедините все в один макрос, а затем воспользуетесь библиотекой Booost Preprocessor для определения своего класса. Посмотрите, как я реализовал макрос 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

0 голосов
/ 05 июня 2009

Я не совсем уверен, что вы ищете в некоторых случаях.

  • Что происходит с foo и bar в спецификации?
  • Что на самом деле возвращает getGroupName? (Foo, бар)? или GroupName?

Похоже, вы пытаетесь создать механизм для загрузки и доступа к структурам на диске произвольной компоновки. Это точно? (Редактировать: только что заметил функцию-член "set" ... так что я думаю, вы ищете полную сериализацию)

Если вы работаете в * nix системе, указание собственного компилятора для компиляции в .o (вероятно, скрипт perl / python / what-have-you, заканчивающийся вызовом gcc) в Makefile - тривиальное решение , Другие могут знать, как это сделать в Windows.

...