C++代码生成
在我的史诗追求使C++做的事情不应该,我试图把编译时间生成的类放在一起。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
}
};
没有人有任何想法,如果这甚至有可能?
- EDIT--
明确此目的的用法。我有我想阅读的标准格式的文件。该格式已经定义,所以不会改变。每个文件可以包含任何数量的记录,每个记录可以包含任何数量的子记录。
许多记录类型每个都包含一组不同的子记录,但它们可以被定义。因此,例如,高度图记录必须包含高度图,但可以包含法线。
所以我想定义一个纪录,像这样:
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++有更深入的理解。
您可能可以使用boost tuples解决此问题。它会产生一种与你现在想的不同的设计,但它应该允许你以通用的方式解决问题。
以下示例定义窗体“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());
}
下面的类实现了我们的录制气构件。使用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);
}
};
下一个类型是我们记录的meta描述。枚举为我们提供了一个可以用来访问成员的象征性名称,即。字段名称。元组则定义类型的字段:
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++代码生成序列化/反序列化数据的方法,我会查看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;
}
一个人C++类产生,让您加载,保存和访问这些数据。您还可以生成蟒蛇,Java等
我会玩的一个记录混入做同样的事情 - 一类自动地在编译时
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");
}
添加功能,我不太确定在某些情况下你正在寻找什么。
- 规格中的foo和bar会发生什么变化?
- getGroupName实际返回什么? (FOO,吧)?或GroupName?
它看起来像你试图创建一个机制来加载和访问任意布局的磁盘结构。这是否准确? (编辑:只注意到“设置”成员函数...所以我想你正在寻找完整的序列化)
如果你在* nix系统,指定你自己的编译器编译为.o(可能一个perl/python/what-have-you脚本完成了对gcc的调用)在Makefile中是一个简单的解决方案。其他人可能知道在Windows上做这件事的方法。
这是我在C和C++中大量使用的一种技术,名为“list macro”。假设你有一系列变量,错误消息,解释器操作码或任何需要写入重复代码的东西。在你的情况下,它是类成员变量。
假设它是变量。把它们放在一个列表宏是这样的:
#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的完美方法。而且,这个列表宏本身就变成了一个mini-DSL。
在C中,是的。通常在C++中,我会首先查找TMP解决方案。这可能有助于解释为什么我在C中更高效。 – 2010-01-14 14:19:28
一般而言,如果将所有内容合并到一个宏中,然后利用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))
我现在可以“访问”名称来自我的解析器成员:
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
这是可能的,我怀疑。你能更精确地知道你到底在哪里? – 2009-06-05 16:51:52
这么做并不明显,你可以从中得到什么好处 - 请解释一下。 – 2009-06-05 16:56:54