La respuesta de Yacoby se puede ampliar aún más.
Creo que la serialización se puede implementar de forma similar a los lenguajes administrados si uno realmente implementa un sistema de reflexión.
Durante años hemos estado utilizando el enfoque automatizado.
Fui uno de los implementadores del postprocesador de C++ en funcionamiento y de la biblioteca Reflection: herramienta LSDC y Linderdaum Engine Core (iObject + RTTI + Linker/Loader). Consulte la fuente en http://www.linderdaum.com
La fábrica de clases abstrae el proceso de creación de instancias de clases.
Para inicializar miembros específicos, puede agregar algunos RTTI intrusivos y generar automáticamente los procedimientos de carga/guardado para ellos.
Supongamos que tiene la clase iObject en la parte superior de su jerarquía.
// Base class with intrusive RTTI
class iObject
{
public:
iMetaClass* FMetaClass;
};
///The iMetaClass stores the list of properties and provides the Construct() method:
// List of properties
class iMetaClass: public iObject
{
public:
virtual iObject* Construct() const = 0;
/// List of all the properties (excluding the ones from base class)
vector<iProperty*> FProperties;
/// Support the hierarchy
iMetaClass* FSuperClass;
/// Name of the class
string FName;
};
// The NativeMetaClass<T> template implements the Construct() method.
template <class T> class NativeMetaClass: public iMetaClass
{
public:
virtual iObject* Construct() const
{
iObject* Res = new T();
Res->FMetaClass = this;
return Res;
}
};
// mlNode is the representation of the markup language: xml, json or whatever else.
// The hierarchy might have come from the XML file or JSON or some custom script
class mlNode {
public:
string FName;
string FValue;
vector<mlNode*> FChildren;
};
class iProperty: public iObject {
public:
/// Load the property from internal tree representation
virtual void Load(iObject* TheObject, mlNode* Node) const = 0;
/// Serialize the property to some internal representation
virtual mlNode* Save(iObject* TheObject) const = 0;
};
/// function to save a single field
typedef mlNode* (*SaveFunction_t)(iObject* Obj);
/// function to load a single field from mlNode
typedef void (*LoadFunction_t)(mlNode* Node, iObject* Obj);
// The implementation for a scalar/iObject field
// The array-based property requires somewhat different implementation
// Load/Save functions are autogenerated by some tool.
class clFieldProperty : public iProperty {
public:
clFieldProperty() {}
virtual ~clFieldProperty() {}
/// Load single field of an object
virtual void Load(iObject* TheObject, mlNode* Node) const {
FLoadFunction(TheObject, Node);
}
/// Save single field of an object
virtual mlNode* Save(iObject* TheObject, mlNode** Result) const {
return FSaveFunction(TheObject);
}
public:
// these pointers are set in property registration code
LoadFunction_t FLoadFunction;
SaveFunction_t FSaveFunction;
};
// The Loader class stores the list of metaclasses
class Loader: public iObject {
public:
void RegisterMetaclass(iMetaClass* C) { FClasses[C->FName] = C; }
iObject* CreateByName(const string& ClassName) { return FClasses[ClassName]->Construct(); }
/// The implementation is an almost trivial iteration of all the properties
/// in the metaclass and calling the iProperty's Load/Save methods for each field
void LoadFromNode(mlNode* Source, iObject** Result);
/// Create the tree-based representation of the object
mlNode* Save(iObject* Source);
map<string, iMetaClass*> FClasses;
};
Al definir la ConcreteClass derivado de IObject, se utiliza alguna extensión y la herramienta de generación de código para producir la lista de guardar/procedimientos de carga y el código de registro para.
Déjanos ver el código para esta muestra.
En algún lugar en el marco tenemos un vacío formal de definir
#define PROPERTY(...)
/// vec3 is a custom type with implementation omitted for brevity
/// ConcreteClass2 is also omitted
class ConcreteClass: public iObject {
public:
ConcreteClass(): FInt(10), FString("Default") {}
/// Inform the tool about our properties
PROPERTY(Name=Int, Type=int, FieldName=FInt)
/// We can also provide get/set accessors
PROPERTY(Name=Int, Type=vec3, Getter=GetPos, Setter=SetPos)
/// And the other field
PROPERTY(Name=Str, Type=string, FieldName=FString)
/// And the embedded object
PROPERTY(Name=Embedded, Type=ConcreteClass2, FieldName=FEmbedded)
/// public field
int FInt;
/// public field
string FString;
/// public embedded object
ConcreteClass2* FEmbedded;
/// Getter
vec3 GetPos() const { return FPos; }
/// Setter
void SetPos(const vec3& Pos) { FPos = Pos; }
private:
vec3 FPos;
};
El código de registro autogenerado sería:
/// Call this to add everything to the linker
void Register_ConcreteClass(Linker* L) {
iMetaClass* C = new NativeMetaClass<ConcreteClass>();
C->FName = "ConcreteClass";
iProperty* P;
P = new FieldProperty();
P->FName = "Int";
P->FLoadFunction = &Load_ConcreteClass_FInt_Field;
P->FSaveFunction = &Save_ConcreteClass_FInt_Field;
C->FProperties.push_back(P);
... same for FString and GetPos/SetPos
C->FSuperClass = L->FClasses["iObject"];
L->RegisterClass(C);
}
// The autogenerated loaders (no error checking for brevity):
void Load_ConcreteClass_FInt_Field(iObject* Dest, mlNode* Val) {
dynamic_cast<ConcereteClass*>Object->FInt = Str2Int(Val->FValue);
}
mlNode* Save_ConcreteClass_FInt_Field(iObject* Dest, mlNode* Val) {
mlNode* Res = new mlNode();
Res->FValue = Int2Str(dynamic_cast<ConcereteClass*>Object->FInt);
return Res;
}
/// similar code for FString and GetPos/SetPos pair with obvious changes
Ahora, si usted tiene la JSON-como la escritura jerárquica
Object("ConcreteClass") {
Int 50
Str 10
Pos 1.5 2.2 3.3
Embedded("ConcreteClass2") {
SomeProp Value
}
}
objeto
el enlazador resolvería todas las clases y propiedades en Guardar/Cargar métodos.
Lo siento por el largo post, la aplicación crece aún más grande cuando todo el manejo de errores viene en
@SergeyK:. Que los cambios recientes? Ciertamente no he oído hablar de ninguno. –
Me refiero a esta respuesta: http://stackoverflow.com/a/10332336/1065190 –
@SergeyK .: ¡Ah! Creo que la sección de comentarios sobre la respuesta en sí es probablemente el mejor lugar para discutirlo. De hecho, ya he comenzado. Me parece bastante esotérico, especialmente la idea de fusionar la serialización con getters y setters automáticos (generalmente es malo mezclar diferentes conceptos). También me recuerda a cierto proyecto de QT ... al final, tienes cuasi-C++ y pierdes la portabilidad porque dependes de la disponibilidad de la herramienta que se supone debe transformarla en C++ apropiado y compilable. No estoy conteniendo la respiración. –