Hay varios problemas con la reflexión en C ++.
Es mucho trabajo agregar, y el comité de C ++ es bastante conservador, y no dedique tiempo a nuevas características radicales a menos que estén seguros de que valdrá la pena. (Se ha hecho una sugerencia para agregar un sistema de módulos similar a los ensamblados .NET, y aunque creo que existe un consenso general de que sería bueno tenerlo, no es su máxima prioridad en este momento, y se ha retrasado hasta mucho después C ++ 0x. La motivación de esta característica es deshacerse del #include
sistema, pero también permitiría al menos algunos metadatos).
No pagas por lo que no usas. Esa es una de las filosofías de diseño básicas que subyacen a C ++. ¿Por qué mi código debe llevar metadatos si nunca lo necesito? Además, la adición de metadatos puede inhibir la optimización del compilador. ¿Por qué debería pagar ese costo en mi código si es posible que nunca necesite esos metadatos?
Lo que nos lleva a otro gran punto: C ++ ofrece muy pocas garantías sobre el código compilado. El compilador puede hacer casi todo lo que quiera, siempre que la funcionalidad resultante sea la esperada. Por ejemplo, no se requiere que sus clases estén realmente
allí . El compilador puede optimizarlos, alinear todo lo que hacen, y con frecuencia hace exactamente eso, porque incluso el código de plantilla simple tiende a crear bastantes instancias de plantilla. La biblioteca estándar de C ++ se basa en esta optimización agresiva. Los functores solo son efectivos si la sobrecarga de instanciar y destruir el objeto puede optimizarse.
operator[]
en un vector solo es comparable a la indexación de matriz cruda en rendimiento porque todo el operador puede alinearse y, por lo tanto, eliminarse por completo del código compilado. C # y Java ofrecen muchas garantías sobre la salida del compilador. Si defino una clase en C #, esa clase existirá en el ensamblaje resultante. Incluso si nunca lo uso. Incluso si todas las llamadas a sus funciones miembro pudieran estar en línea. La clase tiene que estar allí, para que la reflexión pueda encontrarla. Parte de esto se alivia compilando C # en bytecode, lo que significa que el compilador JIT puedeelimine las definiciones de clase y las funciones en línea si lo desea, incluso si el compilador inicial de C # no puede. En C ++, solo tiene un compilador, y tiene que generar un código eficiente. Si se le permitiera inspeccionar los metadatos de un ejecutable de C ++, esperaría ver todas las clases que definió, lo que significa que el compilador tendría que preservar todas las clases definidas, incluso si no son necesarias.
Y luego están las plantillas. Las plantillas en C ++ no se parecen en nada a los genéricos en otros lenguajes. Cada instanciación de plantilla crea un
nuevo tipo. std::vector<int>
es una clase completamente separada de
std::vector<float>
. Eso se suma a muchos tipos diferentes en un programa completo. ¿Qué debería ver nuestra reflexión? La plantilla std::vector
? Pero, ¿cómo puede hacerlo, ya que es una construcción de código fuente, que no tiene sentido en tiempo de ejecución? Tendría que ver las clases separadas
std::vector<int>
y
std::vector<float>
. Y
std::vector<int>::iterator
y
std::vector<float>::iterator
, lo mismo paraconst_iterator
y así. Y una vez que ingresa a la metaprogramación de plantillas, rápidamente termina creando instancias de cientos de plantillas, todas las cuales se vuelven a alinear y eliminar por el compilador. No tienen significado, excepto como parte de un metaprograma en tiempo de compilación. ¿Deberían todos estos cientos de clases ser visibles para la reflexión? Tendrían que hacerlo, porque de lo contrario nuestra reflexión sería inútil, si ni siquiera garantiza que las clases que definí realmente estarán allí . Y un problema secundario es que la clase de plantilla no existe hasta que se instancia. Imagina un programa que usa std::vector<int>
. ¿Debería poder ver nuestro sistema de reflexión std::vector<int>::iterator
? Por un lado, ciertamente lo esperarías. Es una clase importante, y se define en términos de std::vector<int>
, lo que haceexisten en los metadatos. Por otro lado, si el programa nunca usa esta plantilla de clase de iterador, su tipo nunca se habrá instanciado, por lo que el compilador no habrá generado la clase en primer lugar. Y es demasiado tarde para crearlo en tiempo de ejecución, ya que requiere acceso al código fuente.
- Y finalmente, la reflexión no es tan vital en C ++ como lo es en C #. La razón es nuevamente, la metaprogramación de plantilla. No puede resolver todo, pero en muchos casos en los que de otro modo recurriría a la reflexión, es posible escribir un metaprograma que haga lo mismo en tiempo de compilación.
boost::type_traits
Es un ejemplo simple. ¿Quieres saber sobre el tipo
T
? Comprueba su type_traits
. En C #, tendrías que pescar después de su tipo usando la reflexión. La reflexión aún sería útil para algunas cosas (el uso principal que puedo ver, que la metaprogramación no puede reemplazar fácilmente, es para el código de serialización autogenerado), pero conllevaría algunos costos significativos para C ++, y simplemente no es necesario tan a menudo como Está en otros idiomas.
Editar:
en respuesta a los comentarios:
cdleary: Sí, los símbolos de depuración hacen algo similar, ya que almacenan metadatos sobre los tipos utilizados en el ejecutable. Pero también sufren los problemas que describí. Si alguna vez ha intentado depurar una versión de lanzamiento, sabrá a qué me refiero. Existen grandes lagunas lógicas en las que creó una clase en el código fuente, que se eliminó en el código final. Si usara la reflexión para algo útil, necesitaría que fuera más confiable y consistente. Tal como están las cosas, los tipos desaparecerían y desaparecerían casi cada vez que compila. Cambia un pequeño detalle, y el compilador decide cambiar qué tipos se insertan y cuáles no, como respuesta. ¿Cómo extraes algo útil de eso, cuando ' ¿Ni siquiera está garantizado que los tipos más relevantes estarán representados en sus metadatos? El tipo que estaba buscando puede haber estado allí en la última compilación, pero ahora se ha ido. Y mañana, alguien registrará un pequeño cambio inocente en una pequeña función inocente, lo que hace que el tipo sea lo suficientemente grande como para que no quede completamente en línea, por lo que volverá de nuevo. Eso sigue siendo útil para los símbolos de depuración, pero no mucho más que eso. Odiaría intentar generar código de serialización para una clase bajo esos términos. pero no mucho más que eso. Odiaría intentar generar código de serialización para una clase bajo esos términos. pero no mucho más que eso. Odiaría intentar generar código de serialización para una clase bajo esos términos.
Evan Teran: Por supuesto, estos problemas podrían resolverse. Pero eso se remonta a mi punto # 1. Tomaría mucho trabajo, y el comité de C ++ tiene muchas cosas que consideran más importantes. ¿El beneficio de obtener una reflexión limitada (y sería limitada) en C ++ es realmente lo suficientemente grande como para justificar centrarse en eso a expensas de otras características? ¿Existe realmente un gran beneficio al agregar características al lenguaje central que ya se puede hacer (principalmente) a través de bibliotecas y preprocesadores como QT? Quizás, pero la necesidad es mucho menos urgente que si tales bibliotecas no existieran. Sin embargo, para sus sugerencias específicas, creo que no permitirlo en las plantillas lo haría completamente inútil. No podría utilizar la reflexión en la biblioteca estándar, por ejemplo. ¿Qué tipo de reflexión nostd::vector
? Las plantillas son una gran parte de C ++. Una característica que no funciona en las plantillas es básicamente inútil.
Pero tienes razón, se podría implementar alguna forma de reflexión. Pero sería un cambio importante en el idioma. Tal como está ahora, los tipos son exclusivamente una construcción en tiempo de compilación. Existen para el beneficio del compilador, y nada más. Una vez que el código ha sido compilado, no son ninguna clase. Si se estira, podría argumentar que las funciones aún existen, pero en realidad, todo lo que hay es un montón de instrucciones de ensamblador de salto, y una gran cantidad de push / pop de pila. No hay mucho para continuar cuando se agregan dichos metadatos.
Pero como dije, hay una propuesta de cambios en el modelo de compilación, agregando módulos autónomos, almacenando metadatos para tipos seleccionados, permitiendo que otros módulos los hagan referencia sin tener que meterse con #include
s. Es un buen comienzo, y para ser honesto, me sorprende que el comité estándar no haya descartado la propuesta por ser un cambio demasiado grande. Entonces, ¿tal vez en 5-10 años? :)
export
yvector<bool>
.typeinfo
laname()
función DEBE devolver el nombre que escribió el programador y no algo indefinido. Y denos también un stringifier para enumeradores. Esto es realmente crucial para la serialización / deserialización, ayudando a hacer fábricas, etc.La reflexión requiere que algunos metadatos sobre los tipos se almacenen en algún lugar que se pueda consultar. Dado que C ++ se compila en el código de máquina nativo y sufre grandes cambios debido a la optimización, la vista de alto nivel de la aplicación se pierde en el proceso de compilación, por lo tanto, no será posible consultarlos en tiempo de ejecución. Java y .NET utilizan una representación de muy alto nivel en el código binario para máquinas virtuales que hacen posible este nivel de reflexión. Sin embargo, en algunas implementaciones de C ++, hay algo llamado Información de tipo de tiempo de ejecución (RTTI) que puede considerarse una versión simplificada de la reflexión.
fuente
Todos los idiomas no deben tratar de incorporar todas las características de todos los demás idiomas.
C ++ es esencialmente un ensamblador de macros muy, muy sofisticado. NO es (en un sentido tradicional) un lenguaje de alto nivel como C #, Java, Objective-C, Smalltalk, etc.
Es bueno tener diferentes herramientas para diferentes trabajos. Si solo tenemos martillos, todas las cosas se verán como clavos, etc. Tener lenguajes de script es útil para algunos trabajos, y los lenguajes OO reflexivos (Java, Obj-C, C #) son útiles para otra clase de trabajos, y super los lenguajes básicos eficientes y cercanos a la máquina son útiles para otra clase de trabajos (C ++, C, Assembler).
C ++ hace un trabajo increíble al extender la tecnología Assembler a niveles increíbles de gestión de complejidad y abstracciones para hacer que la programación de tareas más grandes y complejas sea mucho más posible para los seres humanos. Pero no es necesariamente un lenguaje que sea el más adecuado para aquellos que están abordando su problema desde una perspectiva estrictamente de alto nivel (Lisp, Smalltalk, Java, C #). Si necesita un idioma con esas características para implementar mejor una solución a sus problemas, ¡agradezca a aquellos que han creado dichos idiomas para que todos los usemos!
Pero C ++ es para aquellos que, por cualquier razón, necesitan tener una fuerte correlación entre su código y la operación de la máquina subyacente. Ya sea su eficiencia, o la programación de controladores de dispositivos, o la interacción con los servicios del sistema operativo de nivel inferior, o lo que sea, C ++ se adapta mejor a esas tareas.
C #, Java, Objective-C requieren un sistema de tiempo de ejecución mucho más grande y rico para admitir su ejecución. Ese tiempo de ejecución debe entregarse al sistema en cuestión, preinstalado para admitir el funcionamiento de su software. Y esa capa tiene que mantenerse para varios sistemas de destino, personalizada por ALGUNO OTRO IDIOMA para que funcione en esa plataforma. Y esa capa intermedia, esa capa adaptativa entre el sistema operativo host y su código, el tiempo de ejecución, casi siempre se escribe en un lenguaje como C o C ++, donde la eficiencia es el número 1, donde la comprensión predecible de la interacción exacta entre software y hardware puede ser buena. entendido y manipulado para obtener la máxima ganancia.
Me encanta Smalltalk, Objective-C y tener un sistema de tiempo de ejecución rico con reflexión, metadatos, recolección de basura, etc. ¡Se puede escribir un código increíble para aprovechar estas instalaciones! Pero eso es simplemente una capa más alta en la pila, una capa que debe descansar en las capas más bajas, que finalmente deben sentarse sobre el sistema operativo y el hardware. Y siempre necesitaremos un lenguaje que sea más adecuado para construir esa capa: C ++ / C / Assembler.
Anexo: C ++ 11/14 continúa expandiendo la capacidad de C ++ para admitir abstracciones y sistemas de nivel superior. El subprocesamiento, la sincronización, los modelos de memoria precisos, las definiciones de máquina abstracta más precisas están permitiendo a los desarrolladores de C ++ lograr muchas de las abstracciones de alto nivel que algunos de estos lenguajes de alto nivel solían tener dominio exclusivo, mientras continuaban proporcionando rendimiento del metal y excelente previsibilidad (es decir, subsistemas de tiempo de ejecución mínimos). Quizás los recursos de reflexión se habilitarán selectivamente en una futura revisión de C ++, para aquellos que lo deseen, o tal vez una biblioteca proporcionará dichos servicios de tiempo de ejecución (¿tal vez haya uno ahora, o el comienzo de uno en impulso?).
fuente
Si realmente desea comprender las decisiones de diseño que rodean a C ++, busque una copia del Manual de referencia de C ++ anotado de Ellis y Stroustrup. NO está actualizado con el último estándar, pero pasa por el estándar original y explica cómo funcionan las cosas y, a menudo, cómo llegaron de esa manera.
fuente
La reflexión para los lenguajes que tiene es sobre la cantidad de código fuente que el compilador está dispuesto a dejar en su código objeto para permitir la reflexión, y cuánta maquinaria de análisis está disponible para interpretar esa información reflejada. A menos que el compilador guarde todo el código fuente, la reflexión estará limitada en su capacidad para analizar los datos disponibles sobre el código fuente.
El compilador de C ++ no guarda nada (bueno, ignorando RTTI), por lo que no obtienes reflejo en el lenguaje. (Los compiladores de Java y C # solo mantienen la clase, los nombres de los métodos y los tipos de retorno, por lo que obtienes un poco de datos de reflexión, pero no puedes inspeccionar las expresiones o la estructura del programa, y eso significa incluso en esos lenguajes "habilitados para la reflexión") la información que puede obtener es bastante escasa y, en consecuencia, realmente no puede hacer mucho análisis).
Pero puede salir del lenguaje y obtener capacidades completas de reflexión. La respuesta a otra discusión de desbordamiento de pila sobre la reflexión en C discute esto.
fuente
La reflexión puede ser y ha sido implementada en c ++ antes.
No es una característica nativa de C ++ porque tiene un alto costo (memoria y velocidad) que el idioma no debe establecer de manera predeterminada: el idioma está orientado al "rendimiento máximo por defecto".
Como no debe pagar por lo que no necesita, y como usted mismo dice que se necesita más en los editores que en otras aplicaciones, debe implementarse solo donde lo necesite y no "forzar" a todo el código ( no necesita reflexionar sobre todos los datos con los que trabajará en un editor u otra aplicación similar).
fuente
La razón por la que C ++ no tiene reflejo es que esto requeriría que los compiladores agreguen información de símbolos a los archivos de objetos, como qué miembros tiene un tipo de clase, información sobre los miembros, sobre las funciones y todo. Esencialmente, esto haría que los archivos de inclusión sean inútiles, ya que la información enviada por las declaraciones se leería de esos archivos de objeto (módulos). En C ++, una definición de tipo puede ocurrir varias veces en un programa al incluir los encabezados respectivos (siempre que todas esas definiciones sean iguales), por lo que habría que decidir dónde colocar la información sobre ese tipo, solo para nombrar uno. complicación aquí La optimización agresiva realizada por un compilador de C ++, que puede optimizar docenas de instancias de plantillas de clase, es otro punto fuerte. Es posible, pero como C ++ es compatible con C,
fuente
Hay toneladas de casos para usar la reflexión en C ++ que no pueden abordarse adecuadamente usando construcciones de tiempo de compilación como la metaprogramación de plantillas.
N3340 propone punteros ricos como una forma de introducir la reflexión en C ++. Entre otras cosas, aborda el problema de no pagar por una función a menos que la use.
fuente
Según Alistair Cockburn, el subtipo no puede garantizarse en un entorno reflexivo .
La reflexión es más relevante para los sistemas de tipeo latente. En C ++, sabes qué tipo tienes y sabes qué puedes hacer con él.
fuente
La reflexión podría ser opcional, como una directiva de preprocesador. Algo como
#pragma enable reflection
De esa manera podemos tener lo mejor de ambos mundos, sin esta biblioteca pragma se crearía sin reflexión (sin ningún tipo de gastos generales como se discutió), luego dependería del desarrollador individual si desean velocidad o facilidad de uso.
fuente
Si C ++ pudiera tener:
const
modificadorconst
modificadorEso sería suficiente para crear bibliotecas muy fáciles de usar en el meollo del procesamiento de datos sin tipo que es tan frecuente en las aplicaciones web y de bases de datos actuales (todos los orms, mecanismos de mensajería, analizadores xml / json, serialización de datos, etc.).
Por ejemplo, la información básica respaldada por la
Q_PROPERTY
macro (parte de Qt Framework) http://qt.nokia.com/doc/4.5/properties.html expandida para cubrir métodos de clase y e) - sería extraordinariamente beneficiosa para C ++ y para La comunidad de software en general.Ciertamente, la reflexión a la que me refiero no cubriría el significado semántico o cuestiones más complejas (como los números de línea de código fuente de comentarios, análisis de flujo de datos, etc.), pero tampoco creo que sean necesarios para ser parte de un estándar de lenguaje.
fuente
Algunos buenos enlaces sobre la reflexión en C ++ que acabo de encontrar:
Documento de trabajo del estándar C ++: aspectos de la reflexión en C ++
Un ejemplo simple de reflexión usando plantillas
fuente
Reflexión en C ++, creo que es de vital importancia si C ++ se va a utilizar como lenguaje para el acceso a la base de datos, el manejo de sesiones web / http y el desarrollo de GUI. La falta de reflexión evita ORM (como Hibernate o LINQ), analizadores XML y JSON que instancinan clases, serialización de datos y muchos otros signos (donde inicialmente se deben usar datos sin tipo para crear una instancia de una clase).
Se puede usar un cambio de tiempo de compilación disponible para un desarrollador de software durante el proceso de compilación para eliminar esta preocupación de 'usted paga por lo que usa'.
Si un desarrollador de firmware no necesita el reflejo para leer datos de un puerto serie, entonces no utilice el conmutador. Pero como desarrollador de bases de datos que quiere seguir usando C ++, estoy constantemente en fase con un código horrible y difícil de mantener que asigna datos entre miembros de datos y construcciones de bases de datos.
Ni la serialización de Boost ni ningún otro mecanismo están realmente resolviendo la reflexión, debe ser realizada por el compilador, y una vez que se haga, C ++ volverá a pensarse en las escuelas y se utilizará en el software que se ocupa del procesamiento de datos.
Para mí, este problema # 1 (y las primitivas de subprocesos ingenuos es el problema # 2).
fuente
Básicamente es porque es un "extra opcional". Muchas personas eligen C ++ sobre lenguajes como Java y C # para que tengan más control sobre la salida del compilador, por ejemplo, un programa más pequeño y / o más rápido.
Si elige agregar reflexión, hay varias soluciones disponibles .
fuente