簡體   English   中英

C ++代碼生成

[英]C++ Code Generation

在我的史詩般的探索中,讓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,但可選包含法線。

所以我想像這樣定義一個Record:

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 ++有更深入的了解。

如果您正在尋找使用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等。

您可以使用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 ());
}

以下類為Record實現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;
}    

由於這是所有模板代碼,您應該能夠將記錄嵌套在記錄中。

這是我在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。

我可能會使用記錄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");

   }

通常,如果將所有內容合並到一個宏中,然后利用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

在某些情況下,我不確定你在尋找什么。

  • 規范中的foo和bar會發生什么?
  • getGroupName實際返回什么? (FOO,吧)? 或GroupName?

看起來您正在嘗試創建一種機制來加載和訪問任意布局的磁盤結構。 這准確嗎? (編輯:剛注意到“設置”成員函數...所以我猜你正在尋找完整的序列化)

如果您使用的是* nix系統,那么在Makefile中指定您自己的編譯器以編譯為.o(可能是perl / python / what-have-you-you腳本,通過調用gcc完成)是一個簡單的解決方案。 其他人可能知道在Windows上執行此操作的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM