Función de enganche en C ++?

Con “conexión” me refiero a la capacidad de anular de forma no intrusiva el comportamiento de una función. Algunos ejemplos:

  • Imprima un mensaje de registro antes y / o después del cuerpo de la función.
  • Envuelva el cuerpo de la función en un cuerpo de prueba de captura.
  • Medir la duración de una función.
  • etc …

He visto diferentes implementaciones en varios lenguajes de progtwigción y bibliotecas:

  • Progtwigción Orientada a Aspectos
  • Funciones de primera clase de JavaScript
  • Patrón decorador OOP
  • Subclasificación de WinAPI
  • method_missing de method_missing
  • La palabra clave %exception SWIG que está destinada a envolver todas las funciones en un bloque try / catch puede ser (ab) utilizada para el enlace

Mis preguntas son:

  • En mi opinión, esta es una característica tan increíblemente útil que me pregunto por qué nunca se ha implementado como una característica del lenguaje C ++. ¿Hay alguna razón que impida que esto sea posible?
  • ¿Cuáles son algunas de las técnicas o bibliotecas recomendadas para implementar esto en un progtwig C ++?

Si está hablando de hacer que se llame a un nuevo método antes / después del cuerpo de una función, sin cambiar el cuerpo de la función, puede basarse en este , que utiliza un shared_ptr shared_ptr personalizado para activar la función after-body. No se puede usar para try/catch , ya que el antes y el después deben ser funciones separadas utilizando esta técnica.

Además, la versión a continuación usa shared_ptr , pero con C ++ 11 debería poder usar unique_ptr para obtener el mismo efecto sin el costo de crear y destruir un puntero compartido cada vez que lo use.

 #include  #include  #include  #include  template  class base_wrapper { protected: typedef T wrapped_type; Derived* self() { return static_cast(this); } wrapped_type* p; struct suffix_wrapper { Derived* d; suffix_wrapper(Derived* d): d(d) {}; void operator()(wrapped_type* p) { d->suffix(p); } }; public: explicit base_wrapper(wrapped_type* p) : p(p) {}; void prefix(wrapped_type* p) { // Default does nothing }; void suffix(wrapped_type* p) { // Default does nothing } boost::shared_ptr operator->() { self()->prefix(p); return boost::shared_ptr(p,suffix_wrapper(self())); } }; template class timing_wrapper : public base_wrapper< T, timing_wrapper > { typedef base_wrapper< T, timing_wrapper > base; typedef boost::chrono::time_point > time_point; time_point begin; public: timing_wrapper(T* p): base(p) {} void prefix(T* p) { begin = boost::chrono::system_clock::now(); } void suffix(T* p) { time_point end = boost::chrono::system_clock::now(); std::cout << "Time: " << (end-begin).count() << std::endl; } }; template  class logging_wrapper : public base_wrapper< T, logging_wrapper > { typedef base_wrapper< T, logging_wrapper > base; public: logging_wrapper(T* p): base(p) {} void prefix(T* p) { std::cout << "entering" << std::endl; } void suffix(T* p) { std::cout << "exiting" << std::endl; } }; template  

Hay características específicas del comstackdor que puede aprovechar, como, por ejemplo, las funciones de documento de GCC. Otros comstackdores probablemente tendrán características similares. Vea esta pregunta SO para detalles adicionales.

Otro enfoque es utilizar algo como la técnica de ajuste de funciones de Bjarne Stroustrup .

Para contestar su primera pregunta:

  • La mayoría de los lenguajes dynamics tienen sus construcciones method_missing , PHP tiene métodos mágicos ( __call y __callStatic ) y Python tiene __getattr__ . Creo que la razón por la que esto no está disponible en C ++ es que va en contra de la naturaleza tipificada de C ++. Implementar esto en una clase significa que cualquier error tipográfico terminará llamando a esta función (¡en tiempo de ejecución!), Lo que evita la captura de estos problemas en el momento de la comstackción. Mezclar C ++ con la escritura de pato no parece ser una buena idea.
  • C ++ intenta ser lo más rápido posible, por lo que las funciones de primera clase están fuera de discusión.
  • AOP. Ahora esto es más interesante, técnicamente, no hay nada que impida que esto se agregue al estándar C ++ (aparte del hecho de que agregar otra capa de complejidad a un estándar que ya es extremadamente complejo podría no ser una buena idea). De hecho, hay comstackdores capaces de mover código, AspectC ++ es uno de ellos. Hace aproximadamente un año no era estable, pero parece que desde entonces lograron lanzar la versión 1.0 con un conjunto de pruebas bastante decente, por lo que podría hacer el trabajo ahora.

Hay un par de técnicas, aquí hay una pregunta relacionada:

Emulando CLOS: antes,: después y: alrededor en C ++ .

En mi opinión, esta es una característica increíblemente útil, ¿por qué no es una característica del lenguaje C ++? ¿Hay alguna razón que impida que esto sea posible?

C ++ el lenguaje no proporciona ningún medio para hacerlo directamente. Sin embargo, tampoco plantea ninguna restricción directa contra esto (AFAIK). Este tipo de característica es más fácil de implementar en un intérprete que en el código nativo, ya que la interpretación es una pieza de software, no una CPU de instrucciones de máquina de transmisión. Bien podría proporcionar un intérprete de C ++ con soporte para ganchos si quisiera.

El problema es por qué la gente usa C ++. Mucha gente está usando C ++ porque quiere una gran velocidad de ejecución. Para lograr ese objective, los comstackdores emiten código nativo en el formato preferido del sistema operativo e intentan codificar tantas cosas en el archivo ejecutable comstackdo. La última parte a menudo significa direcciones de computación en tiempo de comstackción / enlace. Si corrige la dirección de una función en ese momento (o, lo que es peor, en línea con el cuerpo de la función), no habrá más soporte para los ganchos.

Dicho esto, hay formas de hacer que el enganche sea barato, pero requiere extensiones de comstackdor y no es totalmente portátil. Raymond Chen escribió en su blog cómo se implementa la aplicación de parches en la API de Windows. También recomienda contra su uso en el código regular.

Al menos en c ++ framework que utilizo proporciona un conjunto de clases virtuales puras

 class RunManager; class PhysicsManager; // ... 

Cada uno de los cuales define un conjunto de acciones.

 void PreRunAction(); void RunStartAction() void RunStopAction(); void PostRunAction(); 

que son NOP, pero que el usuario puede anular cuando se derive de la clase Parent.

Combine eso con la comstackción condicional (sí, yo sé “¡Yuk!” ) Y puede obtener lo que quiere.

Esto no es una cosa de C ++, pero para lograr algunas de las cosas que menciona, he usado la variable de entorno LD_PRELOAD en los sistemas * nix. Un buen ejemplo de esta técnica en acción es la biblioteca de faketime que se enlaza con las funciones de tiempo.

  1. Tiene que haber una manera de implementar la funcionalidad sin afectar el rendimiento del código que no usa la funcionalidad. C ++ está diseñado según el principio de que solo paga los costos de rendimiento por las funciones que utiliza. Insertar si se verifica en cada función para verificar si se ha anulado, sería inaceptablemente lento para muchos proyectos de C ++. En particular, hacer que funcione para que no haya costo de rendimiento y al mismo tiempo permitir la comstackción independiente de las funciones anuladas y de anulación será complicado. Si solo permites el reemplazo de tiempo de comstackción , entonces es más fácil hacerlo de forma activa (el vinculador puede encargarse de sobrescribir las direcciones), pero estás comparando con ruby ​​y javascript que te permiten cambiar estas cosas en tiempo de ejecución.

  2. Porque subvertiría el sistema de tipos. ¿Qué significa que una función sea privada o no virtual si alguien puede anular su comportamiento de todos modos?

  3. La legibilidad sufriría mucho. ¡Cualquier función puede tener su comportamiento anulado en otra parte del código! Cuanto más contexto necesite para comprender lo que hace una función, más difícil será descubrir una base de código grande. Enganchar es un error, no una característica . Al menos si poder leer lo que escribiste meses después es un requisito.