Создание кода на С++

В моем эпическом стремлении сделать С++ делать что-то не так, я пытаюсь собрать скомпилированный класс, сгенерированный временем.

На основе определения препроцессора, например (грубая концепция)

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

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

Хотя я уверен, что создаю класс, который читает данные из файловой системы, используя эту структуру (возможно, даже делая это с использованием Metaprogramming шаблона), я не вижу, как я могу генерировать обе функции для доступа к данных и функции для чтения данных.

Я хотел бы получить класс, похожий на этот

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

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

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

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

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

Проблема, с которой я сталкиваюсь, заключается в том, что мне нужно каждое определение препроцессора дважды. Один раз для определения определения функции внутри класса и один раз для создания функции чтения. Поскольку препроцессор является чисто функциональным, я не могу подтолкнуть данные к очереди и сгенерировать класс по определению ENC_CLASS marco.

Я не вижу пути вокруг этой проблемы, но задавался вопросом, не сделал ли кто-нибудь, кто лучше понимает С++.

Ответ 1

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

Следующий пример определяет запись формы "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

Если вы ищете способ сериализации/десериализации данных с генерацией кода на С++, я бы посмотрел на Google protobufs (http://code.google.com/p/protobuf/) или Facebook Thrift (http://incubator.apache.org/thrift/).

Для протобуфов вы записываете определение данных так:

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 С++, который позволяет загружать, сохранять и получать доступ к этим данным. Вы также можете создавать python, java и т.д.

Ответ 3

Это метод, который я использую много в 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.

Ответ 4

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

   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

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

Ответ 6

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

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

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

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