¿Cómo puedo agregar reflejo a una aplicación C ++?

263

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

Mella
fuente
18
Mala suerte, no puede hacerlo sin macros y otro preprocesamiento, porque los metadatos requeridos no existen a menos que lo cree manualmente a través de alguna magia de macroprocesamiento.
jalf
66
Sin embargo, la información que puede obtener de RTTI no es suficiente para hacer la mayoría de las cosas sobre las que realmente querría reflexionar. No puede iterar sobre las funciones miembro de una clase, por ejemplo.
Joseph Garvin el

Respuestas:

259

Lo que debe hacer es 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 la expresión escrita. Una expresión escrita es solo una expresión que pone el tipo entre paréntesis. Entonces, en lugar de escribir int x, escribirás (int) x. Aquí hay algunas macros útiles para ayudar 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 una REFLECTABLEmacro 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 Boost.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_nque es el número de campos reflectantes en la clase. Luego se especializa field_datapara cada campo. También es amiga de la reflectorclase, 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 recorrer los campos, usamos el patrón de visitante. Creamos un rango MPL de 0 a la cantidad de campos, y accedemos a los datos de 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, por el momento de la verdad, lo juntamos todo. Así es como podemos definir una Personclase que sea reflectante:

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

Aquí hay una print_fieldsfunción generalizada 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 print_fieldscon la Personclase reflectante :

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.

Paul Fultz II
fuente
106
Felicitaciones por mostrar cómo implementar la reflexión, en lugar de decir que no se puede hacer. Son respuestas como esta las que hacen de SO un gran recurso.
fearless_fool
44
Tenga en cuenta que si intenta compilar esto en Visual Studio, obtendrá un error porque VS no maneja la expansión de macro variadic correctamente. Para VS, intente agregar: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tupley #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__)) y cambie la definición de TYPEOF (x) a:#define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
Phenglei Kai
Recibo el error 'BOOST_PP_IIF_0' no nombra un tipo. ¿Puedes por favor ayudarme?
Ankit Zalani
3
Vea mi propia respuesta: stackoverflow.com/a/28399807/2338477 He extraído y reempacado todas las definiciones, y la biblioteca de impulso no es necesaria. Como código de demostración, estoy proporcionando serialización a xml y restauración desde xml.
TarmoPikaro
106

Hay dos tipos de reflectionnatación alrededor.

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

    Esto no es posible con C ++.
  2. La inspección verificando 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. Úselo boost::type_traitspara muchas cosas (como verificar si un tipo es integral). Para verificar la existencia de una función miembro, use ¿Es posible escribir una plantilla para verificar la existencia de una función? . Para verificar si existe cierto tipo anidado, use SFINAE simple .

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

  • Un Meta Compiler como el Qt Meta Object Compiler que traduce su código agregando metainformaciones adicionales.
  • Un marco que consiste en macros que le permiten agregar las metainformaciones requeridas. Debería decirle al marco todos los métodos, los nombres de clase, las clases base y todo lo que necesita.

C ++ está hecho teniendo en cuenta la velocidad. Si desea una inspección de alto nivel, como C # o Java, entonces me temo que debo decirle que no hay forma sin algún esfuerzo.

Johannes Schaub - litb
fuente
122
C ++ está hecho teniendo en cuenta la velocidad, pero la filosofía no es "lo más rápido posible", sino que es "no se paga si no se usa". Creo que es posible que un lenguaje implemente la introspección de una manera que se ajuste a esa filosofía, C ++ simplemente carece de ella.
Joseph Garvin el
8
@Joseph: ¿Cómo debería hacerse eso? Requeriría que se almacenaran todos esos metadatos. Lo que significa que debe pagarlo, incluso si no lo usa. (A menos que se podía marcar tipos individuales como "reflejo de apoyo", pero entonces estamos casi hasta donde puede ser que también utilice el engaño macro existente.
JALF
25
@jalf: solo los metadatos que podrían ser necesarios. Si consideramos solo la reflexión en tiempo de compilación, esto es trivial. Por ejemplo, una función de tiempo de compilación members<T>que devuelve una lista de todos los miembros de T. Si quisiéramos tener una reflexión en tiempo de ejecución (es decir, RTTI mezclado con reflexión), el compilador aún conocería todos los tipos base reflejados. Es muy probable members<T>(T&)que nunca se cree una instancia para T = std :: string, por lo que no es necesario incluir el RTTI para std :: string o sus clases derivadas.
MSalters
9
La biblioteca reflex (mencionada a continuación) agrega reflexión a C ++ sin ralentizar el código existente en: root.cern.ch/drupal/content/reflex
Joseph Lisee
66
@ Joe: Reflection nunca ralentiza el código existente. Simplemente hace que las cosas entregadas sean más grandes (ya que debe entregar una base de datos de información de tipo ...).
mmmmmmmm
56

Y me encantaría un pony, pero los ponis no son libres. :-pags

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI es lo que vas a obtener. Una reflexión como la que está pensando: metadatos completamente descriptivos disponibles en tiempo de ejecución, simplemente no existe para C ++ de forma predeterminada.

Brad Wilson
fuente
1
Yo segundo Brad. Las plantillas de C ++ pueden ser bastante potentes, y existe una gran experiencia en torno a diversos comportamientos de tipo 'reflexión', como impulsar la biblioteca 'any', rasgos de tipo, C ++ RTTI, etc., que pueden resolver muchos de los problemas para los que se resuelve la reflexión. Nick, ¿cuál es tu objetivo aquí?
Aaron
77
¡Vota por el comentario de los ponis! Votaría dos veces, ya que tu respuesta también lo merece, pero lamentablemente solo recibo una, así que los ponis ganan. :-)
Franci Penov
66
Realmente no entiendo por qué esta es una respuesta inteligente. Ya he dicho que me gustaría referencias a bibliotecas, etc. para implementar esto. La reflexión / introspección es para varios sistemas para permitir el acceso de script, serialización, etc.
Nick
3
@ Nick: Él ya respondió eso. No se puede hacer, los datos no existen y, por lo tanto, ninguna biblioteca puede implementarlos por usted.
jalf
@jalf Todavía es extraño para mí leer a la gente en el mundo de la programación que dice "no es posible" y no "no sé cómo". Claro que los metadatos no existen pero pueden insertarse con macros
Freddx L.
38

La información existe, pero no en el formato que necesita, y solo si exporta sus 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 compile los datos de definición de clase en la DLL / Exe. Pero no está en un formato que pueda usar fácilmente para la reflexión.

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 que las funciones se llamen 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 Invocar (this_pointer, ...) tiene argumentos variables. Obviamente, al llamar a una función de esta manera, está eludiendo cosas como la seguridad constante, etc., por lo que estos aspectos se implementan como verificaciones 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 las 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 podríamos ponerlo en forma para su lanzamiento.

Roderick
fuente
1
Creo que quieres decir __declspec(dllexport)y puedes recuperar la información del archivo .map si habilitas la creación de este durante la compilación.
Orwellophile
19

La reflexión no es compatible con C ++ fuera de la caja. Esto es triste porque hace que las pruebas defensivas sean un dolor.

Hay varios enfoques para hacer la reflexión:

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

El primer enlace parece el más prometedor (usa mod's para hacer sonar), el segundo discute una serie de técnicas, el tercero es un enfoque diferente usando gcc:

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

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

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

Ahora hay un grupo de trabajo para la reflexión en C ++. Vea las noticias para C ++ 14 @ CERN:

Edición 13/08/17:

Desde la publicación original ha habido una serie de posibles avances en la reflexión. Lo siguiente proporciona más detalles y una discusión sobre las diversas técnicas y estado:

  1. Reflexión estática en una cáscara de nuez
  2. Reflexión Estática
  3. Un diseño para la reflexión estática.

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

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

Editar 13/12/2017

La reflexión parece estar avanzando hacia C ++ 20 o más probablemente un TSR. Sin embargo, el movimiento es lento.

Editar 15/09/2018

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

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

Editar 07/11/2019

La reflexión TS está completa y está disponible para comentarios y votaciones durante el verano (2019).

El enfoque de programación de meta-plantilla debe ser reemplazado por un enfoque de código de tiempo de compilación más simple (no reflejado en el TS).

Editar 10/02/2020

Aquí hay una solicitud para admitir la reflexión TS en Visual Studio:

Charla sobre el TS del autor David Sankel:

Editar 17 de marzo de 2020

Se avanza en la reflexión. Aquí puede encontrar un informe del 'Informe de viaje del comité de Praga ISO C ++ 2020-02':

Los detalles sobre lo que se está considerando para C ++ 23 se pueden encontrar aquí (incluye una breve sección sobre Reflexión):

Edición 4 de junio de 2020

Jeff Preshing ha lanzado un nuevo marco llamado 'Plywood' que contiene un mecanismo para la reflexión en tiempo de ejecución. Más detalles se pueden encontrar aquí:

Las herramientas y el enfoque parecen ser los más pulidos y fáciles de usar hasta ahora.

Damian Dixon
fuente
1
El enlace cern está roto.
Colapso de Mostowski
Los enlaces de interés ahora deben ser reparados. Tienden a romperse con bastante frecuencia, lo cual es un dolor.
Damian Dixon
¿Esta respuesta solo se refiere a la reflexión en tiempo de compilación?
einpoklum
@einpoklum las únicas soluciones actuales para la reflexión son el tiempo de compilación, generalmente con código de meta-plantilla o macro. Parece que el último borrador de TS debería funcionar para el tiempo de ejecución, pero habrá tenido que haber creado todas las bibliotecas con el compilador correcto para que se hayan almacenado los metadatos necesarios.
Damian Dixon
@DamianDixon: Eso no es cierto. Hay varias bibliotecas de reflexión en tiempo de ejecución. Ahora, por supuesto, son bastante torpes y son opcionales o requieren asentimientos del compilador, pero aún existen. Si, como entiendo su comentario, solo se refirió a la reflexión en tiempo de compilación, edite su respuesta para que quede más clara.
einpoklum
15

Debe mirar lo que está tratando de hacer y si RTTI satisfará sus requisitos. He implementado mi propia pseudo-reflexión para algunos propósitos muy específicos. Por ejemplo, una vez quise poder configurar de manera flexible lo que generaría una simulación. Se requirió agregar un 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 averiguar qué métodos están disponibles.

Luego, en el archivo de configuración, puede 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 la magia de algunas plantillas 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 geniales.

KeithB
fuente
tengo que amar estas funciones que siempre vuelven verdaderas;) ¿Asumo que esto es inmune a los problemas de pedido de inicio estático?
Paul
14

Yo recomendaría usar Qt .

Hay una licencia de código abierto, así como una licencia comercial.

Jérôme
fuente
1
Miré esto, pero usa macros y el código fuente debe analizarse para generar el código de metadatos. Me gustaría evitar este paso adicional. Prefiero usar una biblioteca C ++ o macros simples. Gracias por la idea sin embargo.
Nick
10
QT, u otra biblioteca que implemente un enfoque similar es lo mejor que obtendrá
jalf
55
Pague en tiempo de compilación o pague en tiempo de ejecución, ¡de cualquier forma que esté pagando!
Martin Beckett
13

¿Qué intentas hacer con la reflexión?
Puede utilizar los rasgos de tipo Boost y las bibliotecas typeof 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.

Ferruccio
fuente
13

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

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

CAMP es una biblioteca con licencia 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 hacerse manualmente.

La biblioteca actual de Tegesoft usa Boost, pero también hay una bifurcación que usa C ++ 11 que ya no requiere Boost .

philant
fuente
11

Hice algo como lo que busca una vez, y aunque es posible obtener cierto nivel de reflexión y acceso a funciones de nivel superior, el dolor de cabeza de mantenimiento podría no valer la pena. Mi sistema se usó para mantener las clases de IU completamente separadas de la lógica de negocios mediante una delegación similar al concepto de paso y reenvío de mensajes de Objective-C. La forma de hacerlo es crear una clase base que sea capaz de mapear símbolos (utilicé un conjunto de cadenas pero podría hacerlo con enumeraciones si prefiere la velocidad y el manejo de errores en tiempo de compilación sobre la flexibilidad total) para que funcionen los punteros (en realidad no punteros de función pura, pero algo similar a lo que Boost tiene con Boost.Function, a la que no tenía acceso en ese momento). Puede hacer lo mismo para sus variables miembro siempre que tenga alguna clase base común capaz de representar cualquier valor. Todo el sistema fue una estafa descarada de codificación y delegación de valores clave, con algunos efectos secundarios que tal vez valieron la gran cantidad de tiempo necesario para que cada clase que usara el sistema combinara todos sus métodos y miembros con llamadas legales : 1) Cualquier clase puede llamar a cualquier método en 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 establecedores de las variables miembro eran fáciles de hacer seguros para subprocesos porque cambiar o acceder a sus valores siempre se hacía a través de 2 métodos en la clase base de todos los objetos. Todo el sistema fue una estafa descarada de codificación y delegación de valores clave, con algunos efectos secundarios que tal vez valieron la gran cantidad de tiempo necesario para que cada clase que usara el sistema combinara todos sus métodos y miembros con llamadas legales : 1) Cualquier clase puede llamar a cualquier método en 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 establecedores de las variables miembro eran fáciles de hacer seguros para subprocesos porque cambiar o acceder a sus valores siempre se hacía a través de 2 métodos en la clase base de todos los objetos. Todo el sistema fue una estafa descarada de codificación y delegación de valores clave, con algunos efectos secundarios que quizás valieron la gran cantidad de tiempo necesario para que cada clase que usara el sistema combinara todos sus métodos y miembros con llamadas legales : 1) Cualquier clase puede llamar a cualquier método en 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 establecedores de las variables miembro eran fáciles de hacer seguros para subprocesos porque cambiar o acceder a sus valores siempre se hacía a través de 2 métodos en la clase base de todos los objetos. 1) Cualquier clase puede llamar a cualquier método en 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 establecedores de las variables miembro eran fáciles de hacer seguros para subprocesos porque cambiar o acceder a sus valores siempre se hacía a través de 2 métodos en la clase base de todos los objetos. 1) Cualquier clase puede llamar a cualquier método en 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 establecedores de las variables miembro eran fáciles de hacer seguros para subprocesos porque cambiar o acceder a sus valores siempre se hacía a través de 2 métodos en la clase base de todos los objetos.

También condujo a la posibilidad de hacer algunas cosas realmente extrañas que de otra manera 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 al pasar un mensaje a todos los elementos de la matriz y recopilar 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 de inmediato a los cambios en los miembros de las clases de back-end en lugar de sondear constantemente los datos o volver a dibujar innecesariamente la pantalla.

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

Desventajas del sistema que pueden disuadirlo 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_castyboost::dynamic_pointer_cast toda tu base de código 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, por lo que no es tan obvio. Los errores tipográficos en sus cadenas tampoco son una sorpresa divertida o fácil de descubrir.

En cuanto a cómo implementar algo como esto: simplemente use punteros compartidos y débiles en alguna base común (el mío se llamaba muy imaginativamente "Objeto") y obtenga todos los tipos que desea usar. Recomiendo instalar Boost.Function en lugar de hacerlo de la manera que lo hice, que fue con un poco de basura personalizada y un montón de macros feas para envolver las llamadas de puntero de función. Como todo está mapeado, inspeccionar objetos es solo cuestión de iterar a través de todas las claves. Dado que mis clases estaban esencialmente lo más cerca posible de una estafa directa de Cocoa usando solo C ++, si quieres algo así, te sugiero que uses la documentación de Cocoa como modelo.

Michel
fuente
Hola, @Michael; ¿todavía tiene el código fuente para esto o lo eliminó? Me gustaría echarle un vistazo si no te importa.
RandomDSdevel
¡Vaya, deletreó mal tu nombre! No es de extrañar que nunca recibí una respuesta ...
RandomDSdevel
10

Hay otra biblioteca nueva para la reflexión en C ++, llamada RTTR (Run Time Type Reflection, ver también github ).

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

Zack
fuente
8

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

1) Use RTTI, que le proporcionará una rutina de arranque para que pueda construir su comportamiento de reflexión, si puede hacer que todas sus clases se 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, deberá 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 usar el SDK de DIA . Si no recuerdo mal, esto le permite abrir pdbs, que debería contener metadatos para sus tipos de C ++. Puede ser suficiente para hacer lo que necesita. Esta página muestra cómo puede obtener todos los tipos base de una clase, por ejemplo.

Sin embargo, ambas soluciones son un poco feas. No hay nada como un poco de C ++ para hacerte apreciar los lujos de C #.

Buena suerte.

usuario4385
fuente
Eso es astuto y un truco gigante, con el DIA SDK que sugeriste allí.
Sqeaky
7

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

Creo que nadie mencionó esto:

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

CERN Reflex . Parece funcionar muy bien.

Germán Diago
fuente
@ j4nbur53 El enlace está roto porque parece que alcanzaron un hito: root.cern.ch
Germán Diago
¿Podría ser que te refieres a este enlace root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Capítulo Reflex?
Colapso Mostowski
Pruebe esto root.cern.ch/how/how-use-reflex . Reflex funciona como un generador que analiza sus archivos de encabezado y genera un código / biblioteca de introspección de C ++, que puede vincular y utilizar una API simple.
Adam Ryczkowski
6

Esta pregunta es un poco vieja ahora (no sé por qué sigo respondiendo preguntas viejas 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, mientras que no sería al revés :)

Realmente creo que una macro para encapsularla BOOST_FUSION_ADAPT_STRUCTpodría generar los métodos necesarios para obtener el comportamiento en tiempo de ejecución.

Matthieu M.
fuente
2
por minghua (quien originalmente editó la publicación): Busqué en esta solución BOOST_FUSION_ADAPT_STRUCT y finalmente encontré un ejemplo. Vea esta nueva pregunta SO: C ++ itera en el campo de estructura anidada con boost fusion adapt_struct .
Matthieu M.
¡Genial, Matthieu! Me acabo de dar cuenta de que he visto tus pistas aquí y allá durante el año pasado. No noté que están relacionados hasta ahora. Esos fueron muy inspiradores.
minghua
6

Creo que le puede interesar el artículo "Uso de plantillas para la reflexión en C ++" de Dominic Filion. Está en la sección 1.4 de Game Programming Gems 5 . Desafortunadamente no tengo mi copia conmigo, pero búsquela porque creo que explica lo que está pidiendo.

Luis
fuente
4

Ponder es una biblioteca de reflexión C ++, en respuesta a esta pregunta. Consideré las opciones y decidí hacer la mía ya que no pude encontrar una que marcara todas mis casillas.

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

Mella
fuente
4

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

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

En "otras técnicas", si no tiene 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 lenguaje para C, C ++, Java, COBOL, PHP, ...

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

Ira Baxter
fuente
3

Puede encontrar otra biblioteca aquí: http://www.garret.ru/cppreflection/docs/reflect.html Admite 2 formas: obtener información de tipo 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, aún no la he probado, pero he probado otras herramientas de este tipo y me gusta cómo funcionan :-)

alariq
fuente
3

Echa un vistazo a Classdesc http://classdesc.sf.net . Proporciona reflexión en forma de "descriptores" de clase, funciona con cualquier compilador estándar de C ++ (sí, se sabe que funciona con Visual Studio y GCC), y no requiere anotaciones de 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.

Russell Standish
fuente
1
Bienvenido a Stack Overflow. Aunque esta respuesta es sobre el tema, es importante señalar que usted es el autor de este software, para dejar en claro que no es una recomendación imparcial :-)
Matthew Strawbridge
2

Cuando quise reflexionar en C ++, leí este artículo y mejoré lo que vi allí. Lo sentimos, no se puede. No soy dueño del resultado ... pero ciertamente puedes obtener lo que tenía e ir desde allí.

Actualmente estoy investigando, cuando me da la gana, métodos para usar herencia_linealmente para hacer que la definición de tipos reflectantes sea mucho más fácil. Llegué bastante lejos en realidad, pero todavía tengo mucho camino por recorrer. Es muy probable que los cambios en C ++ 0x sean de gran ayuda en esta área.

Edward extraño
fuente
2

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

Buscar algunas macros o hacerlas propias. Qt también puede ayudar con la reflexión (si se puede usar).

Bohdan
fuente
2

a pesar de que la reflexión no se admite de forma inmediata en c ++, no es demasiado difícil de implementar. Me he encontrado 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 puede implementar un sistema de reflexión bastante simple y rudimentario. Sin embargo, no es la solución más saludable, y quedan bordes ásperos por resolver, pero para mis necesidades fue suficiente.

la conclusión: la reflexión puede dar sus frutos si se hace correctamente, y es completamente factible en c ++.

Naore Azenkut
fuente
2

Me gustaría anunciar la existencia del kit de herramientas de introspección / reflexión automática "IDK". Utiliza un metacompilador como Qt's y agrega metainformación directamente a los archivos de objetos. 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 IDK

Eugene G
fuente
2

Si está buscando una reflexión C ++ relativamente simple, he recopilado de varias fuentes macro / define, y les comenté cómo funcionan. Puede descargar archivos de encabezado 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 se encuentra en el repositorio de git, aquí: https://github.com/tapika/TestCppReflect/

En parte lo copiaré 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 );

}

REFLECTABLEdefine usa el nombre de clase + nombre de campo con offsetof- para identificar en qué lugar de la memoria se encuentra el campo particular. He intentado recoger la terminología de .NET en la medida de lo posible, pero C ++ y C # son diferentes, por lo que no es de 1 a 1. Todo el modelo de reflexión de C ++ reside en TypeInfoy FieldInfoclases.

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

Entonces la salida producida 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 soporte de clase / estructura de terceros a través de la clase TypeTraits y la 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 SO / compiladores, pero no lo he hecho. (Pregúntame si realmente te gusta la solución, podría ayudarte)

Esta solución es aplicable para la serialización de una sola vez 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

Se puede encontrar información más detallada en el video de YouTube:

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

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

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

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 Usar propiedades C ++ con __declspec(property(get =, put ... ).

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

Aquí se pueden encontrar ejemplos de tales funciones de devolución de llamada virtual:

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

Ver funciones ReflectCopyy función virtual ::OnAfterSetProperty.

Pero como el tema es realmente avanzado, recomiendo revisar primero el video.

Si tiene ideas para mejorar, no dude en ponerse en contacto conmigo.

TarmoPikaro
fuente
2

La biblioteca de Reflexión de acceso aleatorio permite una reflexión bastante fácil e intuitiva: toda la información de campo / tipo está diseñada para estar disponible en matrices o para sentirse como un acceso de matriz. Está escrito para C ++ 17 y funciona con Visual Studios, g ++ y Clang. La biblioteca es solo un encabezado, lo que significa que solo necesita copiar "Reflect.h" en su proyecto para usarlo.

Las estructuras o clases reflejadas necesitan la macro REFLECT, donde se proporciona el nombre de la clase que está reflejando y los nombres de los campos.

class FuelTank {
    public:
        float capacity;
        float currentLevel;
        float tickMarks[2];

    REFLECT(() FuelTank, () capacity, () currentLevel, () tickMarks)
};

Eso es todo lo que hay, no se necesita código adicional para configurar la reflexión. Opcionalmente, puede proporcionar superclases (en paréntesis del primer argumento) y anotaciones de campo (en el paréntesis que precede al campo que desea anotar) para poder atravesar superclases o agregar información adicional en tiempo de compilación a un campo (como Json: :Ignorar).

Recorrer los campos puede ser tan simple como ...

for ( size_t i=0; i<FuelTank::Class::TotalFields; i++ )
    std::cout << FuelTank::Class::Fields[i].name << std::endl;

Puede recorrer una instancia de objeto para acceder a los valores de campo (que puede leer o modificar) y la información del tipo de campo ...

FuelTank::Class::ForEachField(fuelTank, [&](auto & field, auto & value) {
    using Type = typename std::remove_reference<decltype(value)>::type;
    std::cout << TypeToStr<Type>() << " " << field.name << ": " << value << std::endl;
});

Una biblioteca JSON está construida sobre RandomAccessReflection que identifica automáticamente las representaciones de salida JSON apropiadas para leer o escribir, y puede atravesar recursivamente cualquier campo reflejado, así como matrices y contenedores STL.

struct MyOtherObject { int myOtherInt; REFLECT(() MyOtherObject, () myOtherInt) };
struct MyObject
{
    int myInt;
    std::string myString;
    MyOtherObject myOtherObject;
    std::vector<int> myIntCollection;

    REFLECT(() MyObject, () myInt, () myString, (Reflected) myOtherObject, () myIntCollection)
};

int main()
{
    MyObject myObject = {};
    std::cout << "Enter MyObject:" << std::endl;
    std::cin >> Json::in(myObject);
    std::cout << std::endl << std::endl << "You entered:" << std::endl;
    std::cout << Json::pretty(myObject);
}

Lo anterior podría ejecutarse así ...

Enter MyObject:
{
  "myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
  "myOtherObject": {
    "myOtherInt": 9001
  }
}


You entered:
{
  "myInt": 1337,
  "myString": "stringy",
  "myOtherObject": {
    "myOtherInt": 9001
  },
  "myIntCollection": [ 2, 4, 6 ]
}

Ver también...

jjf28
fuente
1

La reflexión en C ++ es muy útil, en caso de que necesite ejecutar algún método para cada miembro (por ejemplo: serialización, hashing, comparación). Vine 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 (ACTUALIZACIÓN):

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 construir 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 10-20 tipos "simples" para admitir cualquier tipo en su proyecto.

Esta macro debe tener una sobrecarga cero para estructurar la creación / destrucción en tiempo de ejecución, y el código de T.EnumerateWith () debe generarse a pedido, lo que se puede lograr haciendo que sea 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 la implementación de un método específico por tipo de miembro es imprescindible en cualquier solución, por lo que no lo asumo como gastos generales.

ACTUALIZACIÓN: hay una implementación muy simple de la macro ENUMERATE_MEMBERS (sin embargo, podría extenderse un poco para admitir la herencia de la 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;)

jenkas
fuente
1

Puede lograr características de reflexión estática interesantes para estructuras con BOOST_HANA_DEFINE_STRUCT de la biblioteca Boost :: Hana.
Hana es bastante versátil, no solo para el caso de uso que tiene en mente, sino también para una gran cantidad de metaprogramaciones de plantillas.

nnolte
fuente
0

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

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

Puede asignar un lugar en la memoria a esa función de esta manera (requiere libdly 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 indirección, puede usarlo dlopenen el binario que llama ( argv[0]).

El único requisito para esto (que no sea dlopen(), libdly dlfcn.h) es conocer los argumentos y el tipo de la función.

SS Anne
fuente