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

13 minutos de lectura

¿Como puedo agregar un reflejo a una aplicacion C
Mella

Me gustaría poder introspeccionar una clase de C++ por su nombre, contenido (es decir, miembros y sus tipos), etc. Estoy hablando de C++ nativo aquí, no de 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?

  • Mala suerte, no puede hacerlo sin macros y otros preprocesamientos, porque los metadatos requeridos no existe a menos que lo cree manualmente a través de alguna magia de preprocesamiento de macros.

    – jalf

    24 de noviembre de 2008 a las 14:31

  • Sin embargo, la información que puede obtener de RTTI no es suficiente para hacer la mayoría de las cosas para las que realmente desearía una reflexión. No puede iterar sobre las funciones miembro de una clase, por ejemplo.

    – José Garvin

    8 de julio de 2009 a las 19:31

¿Como puedo agregar un reflejo a una aplicacion C
Pablo Fultz II

Lo que debe 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 expresión tipeada. Una expresión con tipo es simplemente una expresión que pone el tipo entre paréntesis. Así que en lugar de escribir int x tu escribiras (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 un REFLECTABLE macro para generar los datos sobre cada campo (más el propio campo). Esta macro se llamará así:

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

entonces usando Boost.PP iteramos sobre cada argumento y generamos los datos así:

// 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 de reflector class, 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, usamos el patrón de visitante. Creamos un rango MPL desde 0 hasta el número 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 la hora de la verdad lo ponemos todo junto. Así es como podemos definir un 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 un generalizado print_fields función usando 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.

  • 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.

    – sin miedo_tonto

    13 mayo 2014 a las 16:00

  • Tenga en cuenta que si intenta compilar esto en Visual Studio, obtendrá un error porque VS no maneja correctamente la expansión macro variádica. Para VS, intente agregar: #define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tuple y #define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__)) y cambiando la definición de TYPEOF(x) a: #define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)

    -Phenglei Kai

    27 de noviembre de 2014 a las 3:33


  • Recibo el error ‘BOOST_PP_IIF_0’ no nombra un tipo. ¿Puedes ayudarme?

    – Ankit Zalani

    4 de diciembre de 2015 a las 5:14

  • Vea mi propia respuesta: stackoverflow.com/a/55364085/2338477 He extraído y reempaquetado todas las definiciones, y no se necesita la biblioteca boost. Como código de demostración, estoy proporcionando serialización a xml y restauración desde xml. (Gracias por la corrección @stackprotector)

    – TarmoPikaro

    8 de junio de 2021 a las 16:13

  • Extramadamente útil. Gracias

    –Syed Mishar Newaz

    8 de marzo a las 13:47


1647583214 408 ¿Como puedo agregar un reflejo a una aplicacion C
Johannes Schaub – litb

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 al verificar si un tipo de clase (clase, estructura, unión) tiene un método o tipo anidado, se deriva de otro tipo en particular.

    Este tipo de cosas es posible con C++ usando template-tricks. Utilizar boost::type_traits para 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 un cierto tipo anidado, use simple SFINAE .

Si prefiere buscar formas de lograr 1), como ver 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 metacompilador como Qt Meta Object Compiler que traduce su código agregando metainformación adicional.
  • Un marco que consta de macros que le permiten agregar la metainformación requerida. Debería decirle al marco todos los métodos, los nombres de clase, 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 tengo que decirle que no hay forma sin un poco de esfuerzo.

  • C ++ está hecho pensando en la velocidad, pero la filosofía no es “lo más rápido posible”, sino “no pagas si no lo usas”. Creo que es posible que un lenguaje implemente la introspección de una manera que encaje con esa filosofía, C++ simplemente carece de ella.

    – José Garvin

    8 de julio de 2009 a las 19:34

  • @Joseph: ¿Cómo debería hacerse eso? Requeriría almacenar todos esos metadatos. Lo que significa que tienes que pagar por él, incluso si no lo usas. (A menos que pueda marcar tipos individuales como “reflejo de apoyo”, pero entonces estamos casi abajo donde también podríamos usar el truco macro existente.

    – jalf

    3 de agosto de 2009 a las 11:00

  • @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 en 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 conocería todos los tipos base reflejados. es bastante probable members<T>(T&) nunca se instanciaría para T=std::string, por lo que no es necesario incluir el RTTI para std::string o sus clases derivadas.

    – MSalters

    3 de agosto de 2009 a las 11:22

  • La biblioteca refleja (mencionada a continuación) agrega reflexión a C ++ sin ralentizar el código existente en: root.cern.ch/drupal/content/reflex

    – José Lisée

    4 mayo 2011 a las 19:37

  • @Joe: Reflection nunca ralentiza el código existente. Simplemente hace que las cosas entregadas sean más grandes (ya que tiene que entregar una base de datos de información de tipos…).

    – mmmmmmmm

    14 de febrero de 2012 a las 20:53

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

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

  • Secundo a Brad. Las plantillas de C++ pueden ser bastante poderosas, y existe una gran experiencia en torno a varios comportamientos de tipo de ‘reflexión’, como impulsar ‘cualquier’ biblioteca, 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í?

    – Aarón

    16 de septiembre de 2008 a las 23:19

  • ¡Votar por el comentario de los ponis! Votaría a favor dos veces, ya que tu respuesta también lo merece, pero lamentablemente solo obtuve uno, así que los ponis ganan. 🙂

    – Franci Penov

    11 de noviembre de 2008 a las 3:57

  • Realmente no entiendo por qué esta es una respuesta inteligente. Ya dije que me gustaría referencias a bibliotecas, etc. para implementar esto. La reflexión/introspección es para que varios sistemas permitan el acceso al script, la serialización, etc.

    – Nick

    30 de marzo de 2009 a las 15:10

  • @Nick: Ya respondió eso. No se puede hacer, los datos no existen y, por lo tanto, ninguna biblioteca puede implementarlo por usted.

    – jalf

    3 de agosto de 2009 a las 10:58

  • @jalf Todavía me resulta extraño leer gente en el mundo de la programación que dice que piensa “no es posible” y no “no sé cómo”. Seguro que los metadatos no existen pero se pueden insertar con macros

    – Freddx L.

    13 de abril de 2020 a las 19:44

1647583215 540 ¿Como puedo agregar un reflejo a una aplicacion C
Rodrigo

La información existe, pero no en el formato que necesita, y solo si exporta sus clases. Esto funciona en Windows, no sé acerca de 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 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 llamar a 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á eludiendo cosas como const-safety, etc., por lo que estos aspectos se implementan como controles 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 hay necesidad de derivar de una clase base específica. Si hay suficiente demanda, tal vez podamos ponerlo en forma para su lanzamiento.

  • El enlace cern está roto.

    usuario502187

    25 de diciembre de 2015 a las 23:48

  • Los enlaces cern deberían arreglarse ahora. Tienden a romperse con bastante frecuencia, lo cual es un dolor.

    – Damián Dixon

    13 de agosto de 2017 a las 9:49

  • ¿Esta respuesta solo tiene en cuenta la reflexión en tiempo de compilación?

    – einpoklum

    29/10/2018 a las 16:20

  • @einpoklum, las únicas soluciones actuales para la reflexión son el tiempo de compilación, generalmente con código de metaplantilla o macro. Parece que el último borrador de TS debería funcionar para el tiempo de ejecución, pero tendrá que haber creado todas las bibliotecas con el compilador correcto para que se hayan almacenado los metadatos necesarios.

    – Damián Dixon

    30 de octubre de 2018 a las 8:36

  • @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 nodificaciones del compilador, pero aún existen. Si, según 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

    30 de octubre de 2018 a las 8:47

¿Como puedo agregar un reflejo a una aplicacion C
keithb

Debe ver lo que está tratando de hacer y si RTTI satisfará sus requisitos. Implementé 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. Requería 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() para averiguar 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 cosas realmente geniales.

  • tengo que amar estas funciones que siempre devuelven verdadero;) ¿Supongo que esto es inmune a los problemas de orden de inicio estático?

    – Paula

    16 de marzo de 2015 a las 7:58

1647583215 755 ¿Como puedo agregar un reflejo a una aplicacion C
Kagiso Marvin Molekwa

Yo recomendaría usar Qt.

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

  • tengo que amar estas funciones que siempre devuelven verdadero;) ¿Supongo que esto es inmune a los problemas de orden de inicio estático?

    – Paula

    16 de marzo de 2015 a las 7:58

1647583216 863 ¿Como puedo agregar un reflejo a una aplicacion C
Ferruccio

¿Qué estás tratando de 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.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad