Pregunta

Me gustaría poder hacer una introspección de una clase de C++ para conocer su nombre, contenido (es decir,miembros y sus tipos), etc.Estoy hablando de C++ nativo, no de C++ administrado, que tiene reflexión.Me doy cuenta de que C++ proporciona información limitada utilizando RTTI.¿Qué bibliotecas adicionales (u otras técnicas) podrían proporcionar esta información?

¿Fue útil?

Solución 17

Reflexionar es una biblioteca de reflexión de C++, en respuesta a esta pregunta.Consideré las opciones y decidí hacer la mía propia, ya que no podía encontrar una que cumpliera todos mis requisitos.

Aunque hay excelentes respuestas a esta pregunta, no quiero usar toneladas de macros ni depender de Boost.Boost es una gran biblioteca, pero hay muchos pequeños proyectos C++ 0x personalizados que son más simples y tienen tiempos de compilación más rápidos.También existen ventajas al poder decorar una clase externamente, como empaquetar una biblioteca de C++ que (¿todavía?) no es compatible con C++11.Es una bifurcación de CAMP, que usa C++ 11, que ya no requiere impulso.

Otros consejos

Lo que hay que hacer es hacer que el preprocesador genere datos de reflexión sobre los campos.Estos datos se pueden almacenar como clases anidadas.

Primero, para que sea más fácil y limpio escribirlo en el preprocesador usaremos expresiones escritas.Una expresión escrita es simplemente una expresión que pone el tipo entre paréntesis.Entonces en lugar de escribir int x tu escribiras (int) x.A continuación se muestran algunas macros útiles que le ayudarán con las expresiones escritas:

#define REM(...) __VA_ARGS__
#define EAT(...)

// Retrieve the type
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
// Strip off the type
#define STRIP(x) EAT x
// Show the type without parenthesis
#define PAIR(x) REM x

A continuación, definimos un REFLECTABLE macro para generar los datos sobre cada campo (más el campo en sí).Esta macro se llamará así:

REFLECTABLE
(
    (const char *) name,
    (int) age
)

Entonces usando Impulsar.PP iteramos sobre cada argumento y generamos los datos de esta manera:

// A helper metafunction for adding const to a type
template<class M, class T>
struct make_const
{
    typedef T type;
};

template<class M, class T>
struct make_const<const M, T>
{
    typedef typename boost::add_const<T>::type type;
};


#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
struct field_data {}; \
BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

#define REFLECT_EACH(r, data, i, x) \
PAIR(x); \
template<class Self> \
struct field_data<i, Self> \
{ \
    Self & self; \
    field_data(Self & self) : self(self) {} \
    \
    typename make_const<Self, TYPEOF(x)>::type & get() \
    { \
        return self.STRIP(x); \
    }\
    typename boost::add_const<TYPEOF(x)>::type & get() const \
    { \
        return self.STRIP(x); \
    }\
    const char * name() const \
    {\
        return BOOST_PP_STRINGIZE(STRIP(x)); \
    } \
}; \

Lo que esto hace es generar una constante fields_n ese es el número de campos reflejables en la clase.Luego se especializa el field_data para cada campo.También es amigo del reflector clase, esto es para que pueda acceder a los campos incluso cuando son privados:

struct reflector
{
    //Get field_data at index N
    template<int N, class T>
    static typename T::template field_data<N, T> get_field_data(T& x)
    {
        return typename T::template field_data<N, T>(x);
    }

    // Get the number of fields
    template<class T>
    struct fields
    {
        static const int n = T::fields_n;
    };
};

Ahora, para iterar sobre los campos utilizamos el patrón de visitante.Creamos un rango MPL de 0 a la cantidad de campos y accedemos a los datos del campo en ese índice.Luego pasa los datos del campo al visitante proporcionado por el usuario:

struct field_visitor
{
    template<class C, class Visitor, class I>
    void operator()(C& c, Visitor v, I)
    {
        v(reflector::get_field_data<I::value>(c));
    }
};


template<class C, class Visitor>
void visit_each(C & c, Visitor v)
{
    typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
    boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
}

Ahora para el momento de la verdad lo juntamos todo.Así es como podemos definir una Person clase que es reflejable:

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};

Aquí hay una generalización. print_fields función que utiliza los datos de reflexión para iterar sobre los campos:

struct print_visitor
{
    template<class FieldData>
    void operator()(FieldData f)
    {
        std::cout << f.name() << "=" << f.get() << std::endl;
    }
};

template<class T>
void print_fields(T & x)
{
    visit_each(x, print_visitor());
}

Un ejemplo de uso de la print_fields con el reflejable Person clase:

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}

Qué salidas:

name=Tom
age=82

Y listo, acabamos de implementar la reflexión en C++, en menos de 100 líneas de código.

Hay dos tipos de reflection nadando alrededor.

  1. Inspección iterando sobre miembros de un tipo, enumerando sus métodos, etc.

    Esto no es posible con C++.
  2. Inspección comprobando si un tipo de clase (clase, estructura, unión) tiene un método o tipo anidado, se deriva de otro tipo particular.

    Este tipo de cosas es posible con C++ usando template-tricks.Usar boost::type_traits para muchas cosas (como comprobar si un tipo es integral).Para verificar la existencia de una función miembro, use ¿Es posible escribir una plantilla para comprobar la existencia de una función? .Para comprobar si existe un determinado tipo anidado, utilice simple SFINAE .

Si prefiere buscar formas de lograr 1), como ver cuántos métodos tiene una clase, o obtener la representación de cadena de una identificación de clase, entonces me temo que no existe una forma estándar de C++ de hacer esto.Tienes que usar cualquiera de los dos

  • Un metacompilador como el Qt Meta Object Compiler que traduce su código y agrega metainformación adicional.
  • Un marco que consta de macros que le permiten agregar la metainformación requerida.Necesitaría decirle al marco todos los métodos, los nombres de las clases, las clases base y todo lo que necesita.

C++ está hecho pensando en la velocidad.Si desea una inspección de alto nivel, como C# o Java, me temo que debo decirle que no hay manera sin un poco de esfuerzo.

Y me encantaría tener un pony, pero los ponis no son gratis.:-pag

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI es lo que vas a conseguir.La reflexión en la que estás pensando (metadatos totalmente descriptivos disponibles en tiempo de ejecución) simplemente no existe para C++ de forma predeterminada.

RTTI no existe para C++.

Esto es simplemente incorrecto.En realidad, el término "RTTI" fue acuñado por el estándar C++.Por otra parte, RTTI no llega muy lejos en la implementación de la reflexión.

La información existe, pero no en el formato que necesitas y sólo si exportas tus clases.Esto funciona en Windows, no sé sobre otras plataformas.Usando los especificadores de clase de almacenamiento como en, por ejemplo:

class __declspec(export) MyClass
{
public:
    void Foo(float x);
}

Esto hace que el compilador construya los datos de definición de clase en el archivo DLL/Exe.Pero no está en un formato que puedas utilizar fácilmente para reflexionar.

En mi empresa creamos una biblioteca que interpreta estos metadatos y le permite reflejar una clase sin insertar macros adicionales, etc.en la clase misma.Permite llamar funciones de la siguiente manera:

MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);

Esto efectivamente hace:

instance_ptr->Foo(1.331);

La función Invoke(this_pointer,...) tiene argumentos variables.Obviamente, al llamar a una función de esta manera estás eludiendo cosas como const-safety y demás, por lo que estos aspectos se implementan como comprobaciones de tiempo de ejecución.

Estoy seguro de que la sintaxis podría mejorarse y hasta ahora solo funciona en Win32 y Win64.Lo hemos encontrado realmente útil para tener interfaces GUI automáticas para clases, crear propiedades en C++, transmitir hacia y desde XML, etc., y no es necesario derivar de una clase base específica.Si hay suficiente demanda tal vez podamos darle forma para su lanzamiento.

Debe analizar lo que está intentando hacer y si RTTI satisfará sus requisitos.He implementado mi propia pseudorreflexión para algunos propósitos muy específicos.Por ejemplo, una vez quise poder configurar de manera flexible lo que produciría una simulación.Requirió agregar algún código repetitivo a las clases que se generarían:

namespace {
  static bool b2 = Filter::Filterable<const MyObj>::Register("MyObject");
} 

bool MyObj::BuildMap()
{
  Filterable<const OutputDisease>::AddAccess("time", &MyObj::time);
  Filterable<const OutputDisease>::AddAccess("person", &MyObj::id);
  return true;
}

La primera llamada agrega este objeto al sistema de filtrado, que llama al BuildMap() método para determinar qué métodos están disponibles.

Luego, en el archivo de configuración, puedes hacer algo como esto:

FILTER-OUTPUT-OBJECT   MyObject
FILTER-OUTPUT-FILENAME file.txt
FILTER-CLAUSE-1        person == 1773
FILTER-CLAUSE-2        time > 2000

A través de alguna plantilla mágica que involucra boost, esto se traduce en una serie de llamadas a métodos en tiempo de ejecución (cuando se lee el archivo de configuración), por lo que es bastante eficiente.No recomendaría hacer esto a menos que realmente lo necesites, pero, cuando lo hagas, puedes hacer algunas cosas realmente interesantes.

¿Qué intentas hacer con la reflexión?
Puedes usar el impulso rasgos de tipo y tipo de bibliotecas como una forma limitada de reflexión en tiempo de compilación.Es decir, puede inspeccionar y modificar las propiedades básicas de un tipo pasado a una plantilla.

Yo recomendaría usar cuarto.

Existe una licencia de código abierto y una licencia comercial.

EDITAR: ACAMPAR ya no se mantiene;Hay dos tenedores disponibles:

  • Uno también se llama ACAMPAR también, y se basa en la misma API.
  • Reflexionar es una reescritura parcial y se preferirá ya que no requiere Boost;está usando C ++ 11.

ACAMPAR es una biblioteca con licencia del MIT (anteriormente LGPL) que agrega reflexión al lenguaje C++.No requiere un paso de preprocesamiento específico en la compilación, pero el enlace debe realizarse manualmente.

La biblioteca actual de Tegesoft usa Boost, pero también hay un tenedor usando C ++ 11 que ya no requiere impulso.

Una vez hice algo parecido a lo que buscas y, si bien es posible obtener cierto nivel de reflexión y acceso a funciones de nivel superior, es posible que el dolor de cabeza de mantenimiento no valga la pena.Mi sistema se utilizó para mantener las clases de UI completamente separadas de la lógica empresarial mediante una delegación similar al concepto de paso y reenvío de mensajes de Objective-C.La forma de hacerlo es crear alguna clase base que sea capaz de asignar símbolos (yo usé un grupo de cadenas, pero puedes hacerlo con enumeraciones si prefieres la velocidad y el manejo de errores en tiempo de compilación a la flexibilidad total) a punteros de función (en realidad no punteros de función pura, pero algo similar a lo que Boost tiene con Boost.Function, al que no tenía acceso en ese momento).Puede hacer lo mismo con sus variables miembro siempre que tenga alguna clase base común capaz de representar cualquier valor.Todo el sistema era una copia descarada de la codificación y delegación de valores clave, con algunos efectos secundarios que tal vez valieron la gran cantidad de tiempo necesario para lograr que cada clase que usaba el sistema combinara todos sus métodos y miembros con llamadas legales. :1) Cualquier clase podría llamar a cualquier método de cualquier otra clase sin tener que incluir encabezados o escribir clases base falsas para que la interfaz pueda estar predefinida para el compilador;y 2) Los captadores y definidores de las variables miembro eran fáciles de hacer seguros para subprocesos porque cambiar o acceder a sus valores siempre se realizaba a través de 2 métodos en la clase base de todos los objetos.

También generó la posibilidad de hacer algunas cosas realmente raras que de otro modo no serían fáciles en C++.Por ejemplo, podría crear un objeto Array que contuviera elementos arbitrarios de cualquier tipo, incluido él mismo, y crear nuevas matrices dinámicamente pasando un mensaje a todos los elementos de la matriz y recopilando los valores de retorno (similar al mapa en Lisp).Otra fue la implementación de la observación de valores clave, mediante la cual pude configurar la interfaz de usuario para responder inmediatamente a los cambios en los miembros de las clases de backend en lugar de sondear constantemente los datos o volver a dibujar la visualización innecesariamente.

Quizás lo más interesante para usted sea el hecho de que también puede volcar todos los métodos y miembros definidos para una clase, y nada menos que en forma de cadena.

Desventajas del sistema que podrían disuadirle de molestarse:agregar todos los mensajes y valores-clave es extremadamente tedioso;es más lento que sin ningún reflejo;llegarás a odiar ver boost::static_pointer_cast y boost::dynamic_pointer_cast por todo tu código base con una pasión violenta;Las limitaciones del sistema fuertemente tipado todavía están ahí, en realidad solo las estás ocultando un poco para que no sea tan obvio.Los errores tipográficos en las cadenas tampoco son una sorpresa divertida ni fácil de descubrir.

En cuanto a cómo implementar algo como esto:simplemente use punteros compartidos y débiles a alguna base común (la mía se llamó muy imaginativamente "Objeto") y derive para todos los tipos que desee usar.Recomendaría instalar Boost.Function en lugar de hacerlo como lo hice yo, que fue con algunas tonterías personalizadas y un montón de macros feas para ajustar las llamadas al puntero de función.Dado que todo está mapeado, inspeccionar objetos es solo cuestión de recorrer todas las claves.Dado que mis clases eran esencialmente lo más parecidas posible a una copia directa de Cocoa usando solo C++, si quieres algo así, te sugiero que uses la documentación de Cocoa como modelo.

Las dos soluciones tipo reflexión que conozco de mis días en C++ son:

1) Utilice RTTI, que le proporcionará un arranque para que pueda desarrollar su comportamiento similar a la reflexión, si puede hacer que todas sus clases deriven de una clase base de 'objeto'.Esa clase podría proporcionar algunos métodos como GetMethod, GetBaseClass, etc.En cuanto a cómo funcionan esos métodos, necesitará agregar manualmente algunas macros para decorar sus tipos, que detrás de escena crean metadatos en el tipo para proporcionar respuestas a GetMethods, etc.

2) Otra opción, si tiene acceso a los objetos del compilador, es utilizar el SDK de dia.Si no recuerdo mal, esto te permite abrir pdbs, que deberían contener metadatos para tus tipos de C++.Puede que sea suficiente para hacer lo que necesitas. Esta página muestra cómo puede obtener todos los tipos base de una clase, por ejemplo.

¡Ambas soluciones son un poco feas!No hay nada como un poco de C++ para hacerte apreciar los lujos de C#.

Buena suerte.

Hay otra nueva biblioteca para la reflexión en C++, llamada RTTR (Reflexión del tipo de tiempo de ejecución, consulte también github).

La interfaz es similar a la reflexión en C# y funciona sin ningún RTTI.

EDITAR:Enlace roto actualizado a partir del 7 de febrero de 2017.

Creo que nadie mencionó esto:

En el CERN utilizan un sistema de reflexión completo para C++:

Reflejo del CERN.Parece funcionar muy bien.

C++ no admite Reflection de fábrica.Esto es triste porque hace que las pruebas defensivas sean una molestia.

Hay varios enfoques para hacer reflexión:

  1. utilice la información de depuración (no portátil).
  2. Espolvoree su código con macros/plantillas o algún otro enfoque fuente (se ve feo)
  3. Modifique un compilador como clang/gcc para producir una base de datos.
  4. Utilice el enfoque Qt moc
  5. Impulsar Reflejar
  6. Reflexión precisa y plana

El primer enlace parece el más prometedor (usa mods para sonar), el segundo analiza una serie de técnicas, el tercero es un enfoque diferente que utiliza gcc:

  1. http://www.donw.org/rfl/

  2. https://bitbucket.org/dwilliamson/clreflect

  3. https://root.cern.ch/how/how-use-reflex

Ahora existe un grupo de trabajo para la reflexión de C++.Vea las novedades para C++14 @ CERN:

Editar 13/08/17:Desde la publicación original ha habido una serie de avances potenciales en la reflexión.A continuación se proporcionan más detalles y una discusión sobre las diversas técnicas y estados:

  1. La reflexión estática en pocas palabras
  2. Reflexión estática
  3. Un diseño para la reflexión estática.

Sin embargo, no parece prometedor un enfoque de reflexiones estandarizadas en C++ en el futuro cercano a menos que haya mucho más interés por parte de la comunidad en apoyar la reflexión en C++.

A continuación se detalla el estado actual según los comentarios de la última reunión de estándares de C++:

Editar 13/12/2017

Parece que Reflection avanza hacia C++ 20 o más, probablemente un TSR.Sin embargo, el movimiento es lento.

Editar 15/09/2018

Se ha enviado un borrador de TS a los organismos nacionales para su votación.

El texto se puede encontrar aquí: https://github.com/cplusplus/reflection-ts

Esta pregunta es un poco antigua ahora (no sé por qué sigo respondiendo a preguntas antiguas hoy) pero estaba pensando en BOOST_FUSION_ADAPT_STRUCT que introduce la reflexión en tiempo de compilación.

Depende de usted asignar esto a la reflexión en tiempo de ejecución, por supuesto, y no será demasiado fácil, pero es posible en esta dirección, aunque no en la inversa :)

Realmente creo que una macro para encapsular el BOOST_FUSION_ADAPT_STRUCT se podrían generar los métodos necesarios para obtener el comportamiento en tiempo de ejecución.

Creo que puede resultarle interesante el artículo "Uso de plantillas para la reflexión en C++" de Dominic Filion.Está en la sección 1.4 de Gemas de programación de juegos 5.Lamentablemente no tengo mi copia conmigo, pero búscala porque creo que explica lo que estás pidiendo.

La reflexión se trata esencialmente de lo que el compilador decidió dejar como huellas en el código que el código en tiempo de ejecución puede consultar.C++ es famoso por no pagar por lo que no usas;Debido a que la mayoría de la gente no usa/quiere reflexión, el compilador de C++ evita el costo al no registrar cualquier cosa.

Por lo tanto, C++ no proporciona reflexión y no es fácil "simularlo" usted mismo como regla general, como lo han señalado otras respuestas.

En "otras técnicas", si no tienes un lenguaje con reflexión, obtenga una herramienta que pueda extraer la información que desea en el momento de la compilación.

Nuestro Kit de herramientas de reingeniería de software DMS es una tecnología de compilación generalizada parametrizada por definiciones de lenguaje explícitas.Tiene definiciones de idiomas para C, C++, Java, COBOL, PHP, ...

Para las versiones C, C++, Java y COBOL, proporciona acceso completo a árboles de análisis e información de tablas de símbolos.Esa información de la tabla de símbolos incluye el tipo de datos que probablemente desee de la "reflexión".Si su objetivo es enumerar algún conjunto de campos o métodos y hacer algo con ellos, DMS se puede usar para transformar el código de acuerdo con lo que encuentre en las tablas de símbolos de manera arbitraria.

Puedes encontrar otra biblioteca aquí: http://www.garret.ru/cppreflection/docs/reflect.htmlAdmite 2 formas:obtener información de tipo a partir de la información de depuración y dejar que el programador proporcione esta información.

También me interesó la reflexión para mi proyecto y encontré esta biblioteca. No la he probado todavía, pero probé otras herramientas de este tipo y me gusta cómo funcionan :-)

Echa un vistazo a Classdesc http://classdesc.sf.net.Proporciona reflexión en forma de "descriptores" de clases, funciona con cualquier compilador estándar de C++ (sí, se sabe que funciona tanto con Visual Studio como con GCC) y no requiere anotaciones en el código fuente (aunque existen algunos pragmas para manejar situaciones difíciles). ).Ha estado en desarrollo durante más de una década y se ha utilizado en varios proyectos a escala industrial.

Cuando quería una reflexión en C++ leí Este artículo y mejoré lo que vi allí.Lo sentimos, no hay lata.No soy dueño del resultado... pero ciertamente puedes obtener lo que yo tenía y seguir desde ahí.

Actualmente estoy investigando, cuando me apetezca, métodos para utilizar hered_linearly para facilitar mucho la definición de tipos reflejables.En realidad, he llegado bastante lejos, pero todavía me queda mucho camino por recorrer.Es muy probable que los cambios en C++0x sean de gran ayuda en esta área.

Parece que C++ todavía no tiene esta característica.Y C++11 reflexión pospuesta también ((

Busque algunas macros o cree las propias.Qt también puede ayudar con la reflexión (si se puede utilizar).

Intenta mirar este proyecto. http://www.garret.ru/cppreflection/docs/reflect.htmlSe agregan reflexiones a C++.Agregó metadatos a las clases que luego puedes usar.

Aunque la reflexión no es compatible de forma inmediata con C++, no es demasiado difícil de implementar.Me encontré con este gran artículo:http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

El artículo explica con gran detalle cómo se puede implementar un sistema de reflexión bastante simple y rudimentario.Es cierto que no es la solución más saludable y quedan asperezas por solucionar, pero para mis necesidades fue suficiente.

La conclusión es que la reflexión puede dar sus frutos si se hace correctamente y es completamente factible en C++.

Me gustaría anunciar la existencia del kit de herramientas de introspección/reflexión automática "IDK".Utiliza un metacompilador como el de Qt y agrega metainformación directamente a los archivos objeto.Se afirma que es fácil de usar.Sin dependencias externas.Incluso le permite reflejar automáticamente std::string y luego usarlo en scripts.Por favor mira a NO SÉ

La reflexión en C++ es muy útil, en los casos en que necesite ejecutar algún método para cada miembro (por ejemplo:serialización, hash, comparación).Llegué con una solución genérica, con una sintaxis muy simple:

struct S1
{
    ENUMERATE_MEMBERS(str,i);
    std::string str;
    int i;
};
struct S2
{
    ENUMERATE_MEMBERS(s1,i2);
    S1 s1;
    int i2;
};

Donde ENUMERATE_MEMBERS es una macro, que se describe más adelante (ACTUALIZAR):

Supongamos que hemos definido la función de serialización para int y std::string de esta manera:

void EnumerateWith(BinaryWriter & writer, int val)
{
    //store integer
    writer.WriteBuffer(&val, sizeof(int));
}
void EnumerateWith(BinaryWriter & writer, std::string val)
{
    //store string
    writer.WriteBuffer(val.c_str(), val.size());
}

Y tenemos una función genérica cerca de la "macro secreta";)

template<typename TWriter, typename T>
auto EnumerateWith(TWriter && writer, T && val) -> is_enumerable_t<T>
{
    val.EnumerateWith(write); //method generated by ENUMERATE_MEMBERS macro
}

Ahora puedes escribir

S1 s1;
S2 s2;
//....
BinaryWriter writer("serialized.bin");

EnumerateWith(writer, s1); //this will call EnumerateWith for all members of S1
EnumerateWith(writer, s2); //this will call EnumerateWith for all members of S2 and S2::s1 (recursively)

Entonces, al tener la macro ENUMERATE_MEMBERS en la definición de estructura, puede crear serialización, comparación, hash y otras cosas sin tocar el tipo original, el único requisito es implementar el método "EnumerateWith" para cada tipo, que no es enumerable, por enumerador (como BinaryWriter) .Por lo general, tendrá que implementar entre 10 y 20 tipos "simples" para admitir cualquier tipo en su proyecto.

Esta macro no debe tener sobrecarga para la creación/destrucción de estructuras en tiempo de ejecución, y el código de T.EnumerateWith() debe generarse bajo demanda, lo que se puede lograr convirtiéndola en una función de plantilla en línea, por lo que la única sobrecarga en toda la historia es agregar ENUMERATE_MEMBERS(m1,m2,m3...) a cada estructura, mientras que implementar un método específico por tipo de miembro es imprescindible en cualquier solución, por lo que no lo asumo como una sobrecarga.

ACTUALIZAR:Existe una implementación muy simple de la macro ENUMERATE_MEMBERS (sin embargo, podría ampliarse un poco para admitir la herencia de una estructura enumerable)

#define ENUMERATE_MEMBERS(...) \
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) const { EnumerateWithHelper(enumerator, __VA_ARGS__ ); }\
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) { EnumerateWithHelper(enumerator, __VA_ARGS__); }

// EnumerateWithHelper
template<typename TEnumerator, typename ...T> inline void EnumerateWithHelper(TEnumerator & enumerator, T &...v) 
{ 
    int x[] = { (EnumerateWith(enumerator, v), 1)... }; 
}

// Generic EnumerateWith
template<typename TEnumerator, typename T>
auto EnumerateWith(TEnumerator & enumerator, T & val) -> std::void_t<decltype(val.EnumerateWith(enumerator))>
{
    val.EnumerateWith(enumerator);
}

Y no necesita ninguna biblioteca de terceros para estas 15 líneas de código;)

Con C++20, tienes declaraciones de expansión, que le permite iterar sobre tipos de agregados:

struct my_type {
    double data;
    std::string another_data;
    int last_data;
};

auto object = my_type{};

for...(auto& member : object) {
    using member_type = std::remove_cvref_t<decltype(member)>;
    member = get_data<member_type>();
}

Si está buscando una reflexión de C++ relativamente simple, he recopilado macros/definiciones de varias fuentes y les he comentado cómo funcionan.Puede descargar el encabezado archivos desde aquí:

https://github.com/tapika/TestCppReflect/blob/master/MacroHelpers.h

conjunto de definiciones, además de funcionalidad además:

https://github.com/tapika/TestCppReflect/blob/master/CppReflect.h https://github.com/tapika/TestCppReflect/blob/master/CppReflect.cpp https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h

La aplicación de muestra también reside en el repositorio de git, aquí:https://github.com/tapika/TestCppReflect/

Lo copiaré parcialmente aquí con una explicación:

#include "CppReflect.h"
using namespace std;


class Person
{
public:

    // Repack your code into REFLECTABLE macro, in (<C++ Type>) <Field name>
    // form , like this:

    REFLECTABLE( Person,
        (CString)   name,
        (int)       age,
...
    )
};

void main(void)
{
    Person p;
    p.name = L"Roger";
    p.age = 37;
...

    // And here you can convert your class contents into xml form:

    CStringW xml = ToXML( &p );
    CStringW errors;

    People ppl2;

    // And here you convert from xml back to class:

    FromXml( &ppl2, xml, errors );
    CStringA xml2 = ToXML( &ppl2 );
    printf( xml2 );

}

REFLECTABLE definir usa nombre de clase + nombre de campo con offsetof - identificar en qué lugar de la memoria se encuentra un campo particular.Intenté retomar la terminología de .NET en la medida de lo posible, pero C++ y C# son diferentes, por lo que no es 1 a 1.Todo el modelo de reflexión de C++ reside en TypeInfo y FieldInfo clases.

He utilizado el analizador pugi xml para recuperar el código de demostración en xml y restaurarlo desde xml.

Entonces, el resultado producido por el código de demostración se ve así:

<?xml version="1.0" encoding="utf-8"?>
<People groupName="Group1">
    <people>
        <Person name="Roger" age="37" />
        <Person name="Alice" age="27" />
        <Person name="Cindy" age="17" />
    </people>
</People>

También es posible habilitar cualquier compatibilidad con clases/estructuras de terceros a través de la clase TypeTraits y una especificación de plantilla parcial; para definir su propia clase TypeTraitsT, de manera similar a CString o int; consulte el código de ejemplo en

https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h#L195

Esta solución es aplicable para Windows/Visual Studio.Es posible portarlo a otros sistemas operativos/compiladores, pero no lo he hecho.(Pregúntame si realmente te gusta la solución, tal vez pueda ayudarte)

Esta solución es aplicable para la serialización única de una clase con múltiples subclases.

Sin embargo, si está buscando un mecanismo para serializar partes de clase o incluso para controlar qué funcionalidad producen las llamadas de reflexión, puede echar un vistazo a la siguiente solución:

https://github.com/tapika/cppscriptcore/tree/master/SolutionProjectModel

Puede encontrar información más detallada en el vídeo de YouTube:

Reflexión del tipo de tiempo de ejecución de C++https://youtu.be/TN8tJijkeFE

Estoy tratando de explicar un poco más profundamente cómo funcionará la reflexión en C++.

El código de muestra se verá así, por ejemplo:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/testCppApp.cpp

c.General.IntDir = LR"(obj\$(ProjectName)_$(Configuration)_$(Platform)\)";
c.General.OutDir = LR"(bin\$(Configuration)_$(Platform)\)";
c.General.UseDebugLibraries = true;
c.General.LinkIncremental = true;
c.CCpp.Optimization = optimization_Disabled;
c.Linker.System.SubSystem = subsystem_Console;
c.Linker.Debugging.GenerateDebugInformation = debuginfo_true;

Pero cada paso aquí en realidad da como resultado una llamada a la función Uso de propiedades de C++ con __declspec(property(get =, put ... ).

que recibe información completa sobre los tipos de datos de C++, los nombres de propiedades de C++ y los punteros de instancia de clase, en forma de ruta, y en base a esa información puede generar xml, json o incluso serializarlo a través de Internet.

Se pueden encontrar ejemplos de dichas funciones de devolución de llamada virtual aquí:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/VCConfiguration.cpp

Ver funciones ReflectCopy, y función virtual ::OnAfterSetProperty.

Pero como el tema es muy avanzado, recomiendo ver el vídeo primero.

Si tiene algunas ideas de mejora, no dude en ponerse en contacto conmigo.

El proyecto Root Reflex tiene soporte para esto.

Ver https://root.cern.ch/how/how-use-reflex

Si declaras un puntero a una función como esta:

int (*func)(int a, int b);

Puedes asignar un lugar en la memoria a esa función de esta manera (requiere libdl y dlopen)

#include <dlfcn.h>

int main(void)
{
    void *handle;
    char *func_name = "bla_bla_bla";
    handle = dlopen("foo.so", RTLD_LAZY);
    *(void **)(&func) = dlsym(handle, func_name);
    return func(1,2);
}

Para cargar un símbolo local usando dirección indirecta, puede usar dlopen en el binario que llama (argv[0]).

El único requisito para esto (aparte de dlopen(), libdl, y dlfcn.h) es conocer los argumentos y el tipo de función.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top