[英]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
在某些情況下,我不確定你在尋找什么。
看起來您正在嘗試創建一種機制來加載和訪問任意布局的磁盤結構。 這准確嗎? (編輯:剛注意到“設置”成員函數...所以我猜你正在尋找完整的序列化)
如果您使用的是* nix系統,那么在Makefile中指定您自己的編譯器以編譯為.o(可能是perl / python / what-have-you-you腳本,通過調用gcc完成)是一個簡單的解決方案。 其他人可能知道在Windows上執行此操作的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.