Pregunta

Estoy usando plantillas de C ++ para pasar Functores de estrategia para cambiar el comportamiento de mi función. Funciona bien. El functor que paso es una clase sin estado sin almacenamiento y solo sobrecarga al operador () de la forma clásica del funtor.

template <typename Operation> int foo(int a) 
{
int b=Operation()(a);
/* use b here, etc */
}

Hago esto a menudo, y funciona bien, ¡y a menudo hago plantillas con 6 o 7 funtores de plantilla pasados!

Sin embargo, me preocupa tanto la elegancia del código como la eficiencia. El functor no tiene estado, por lo que supongo que el constructor Operation () es gratuito y la evaluación del functor es tan eficiente como una función en línea, pero como todos los programadores de C ++, siempre tengo dudas.

Mi segunda pregunta es si podría usar un enfoque de functor alternativo ... uno que no anule al operador (), ¡pero hace todo en el constructor como efecto secundario! Algo así como:

struct Operation {
  Operation(int a, int &b) { b=a*a; }
};
template <typename Operation> int foo(int a) 
 {
   int b;
   Operation(a,b);
    /* use b here, etc */
 }

Nunca he visto a nadie usar un constructor como "trabajo". de un functor, pero parece que debería funcionar. ¿Hay alguna ventaja? ¿Alguna desventaja? Me gusta la eliminación del extraño paréntesis doble " Operador () (a) " , pero es probable que sea solo estética.

¿Fue útil?

Solución

  

¿Alguna desventaja?

  • Los Ctors no devuelven ningún valor útil: no se pueden usar en llamadas encadenadas (por ejemplo, foo (bar ()).
  • Pueden lanzar.
  • Punto de vista del diseño: los controladores son funciones de creación de objetos, en realidad no pretenden ser caballos de trabajo.

Otros consejos

  1. Los compiladores en realidad alinean el constructor vacío de Operation (al menos gcc en situaciones similares, excepto cuando desactivó la optimización)
  2. La desventaja de hacer todo en el constructor es que no puede crear un functor con algún estado interno de esta manera, por ejemplo. Functor para contar el número de elementos que satisfacen un predicado. Además, el uso de un método de un objeto real como functor le permite almacenar la instancia para su posterior ejecución, algo que no puede hacer con su enfoque de constructor.

Desde un punto de vista de rendimiento, el código demostrado se optimiza por completo con VC y GCC. Sin embargo, una mejor estrategia a menudo es tomar el functor como parámetro, de esa manera obtienes mucha más flexibilidad y características de rendimiento idénticas.

Recomendaría definir un funtor que funcione con los contenedores STL, es decir, deberían implementar el operador (). (Seguir la API del idioma que está utilizando es siempre una buena idea).

Eso permite que sus algoritmos sean muy genéricos (funciones de paso, functores, stl-bind, boost :: function, boost :: bind, boost :: lambda, ...) que es lo que generalmente se quiere.

De esta manera, no necesita especificar el tipo de functor como parámetro de plantilla, solo construya una instancia y páselo:

my_algorithm(foo, bar, MyOperation())

No parece tener sentido implementar el constructor en otra clase.
Todo lo que está haciendo es romper la encapsulación y configurar su clase para el abuso.

Se supone que el constructor inicializa el objeto en un buen estado según lo define la clase. Estás permitiendo que otro objeto inicialice tu clase. ¿Qué garantías tienes de que esta clase de plantilla sepa cómo inicializar tu clase correctamente? Un usuario de su clase puede proporcionar cualquier objeto que pueda alterar el estado interno de su objeto en formas no previstas.

La clase debe ser autónoma e inicializarse en un buen estado. Lo que parece que estás haciendo es jugar con plantillas solo para ver lo que pueden hacer.

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