Создание кода на С++
В моем эпическом стремлении сделать С++ делать что-то не так, я пытаюсь собрать скомпилированный класс, сгенерированный временем.
На основе определения препроцессора, например (грубая концепция)
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, является простое решение. Другие могут знать способы сделать это на окнах.