2010-03-31 22 views
6

Tengo que cargar una matriz de doubles de un archivo, multiplicar cada elemento por un valor en una tabla (diferentes valores para diferentes elementos), hacer algún trabajo en él, invertir la multiplicación (es decir , divide) y luego guarda los datos nuevamente en el archivo.Cómo pasar operadores como parámetros

Actualmente implemento el proceso de multiplicación y división en dos métodos separados. Ahora hay algo de trabajo extra detrás de la escena, pero aparte de las declaraciones específicas donde ocurre la multiplicación/división, el resto del código es idéntico. Como se puede imaginar, con este enfoque debe tener mucho cuidado al realizar cualquier cambio. El código circundante no es trivial, por lo que es un caso de editar manualmente cada método o copiar los cambios de un método al otro y recordar cambiar los operadores * y /.

Después de demasiadas llamadas cerradas, estoy harto de esto y me gustaría realizar una función común que implemente la lógica común y dos funciones de envoltura que pasan a cada operador para usar como parámetro.

Mi planteamiento inicial era utilizar los punteros de función:

void MultiplyData(double data) 
{ TransformData(data, &(operator *)); } 

void DivideData(double data) 
{ TransformData(data, &(operator /)); } 

void TransformData(double data, double (*func)(double op1, double op2)) 
{ /* Do stuff here... */ } 

Sin embargo, no puedo dejar pasar los operadores como punteros (es esto porque es un operador en un tipo nativo?), Así que trataron de usar objetos de función. Al principio pensé que multiplies y divides funtores en <functional> serían ideales:

void MultiplyData(double data) 
{ 
    std::multiplies<double> multFunct; 
    TransformData(data, &multFunct); 
} 

void DivideData(double data) 
{ 
    std::divides<double> divFunct; 
    TransformData(data, &divFunct); 
} 

void TransformData(double data, std::binary_function<double, double, double> *funct) 
{ /* Do stuff here... */ } 

Como se puede ver que estaba tratando de utilizar un puntero de clase base para pasar el funtor polimórfica. El problema es que std::binary_function no declara un miembro operator() para implementar las clases secundarias.

¿Hay algo que me falta, o es la solución para implementar mi propia jerarquía de funtores (que realmente parece más problemática de lo que vale)?

+0

¿Qué es 'TransformData'? –

+0

'TransformData' es mi propia función que transforma' datos' usando la función apuntada por 'funct'. –

Respuesta

11

Hacer TransformData una función de plantilla:

template <typename F> 
typename F::result_type TransformData(double data, F f) { ... } 

Llamada así:

double MultiplyData(double data) { 
    return TransformData(data, std::multiplies<double>()); 
} 

std :: binary_function es una clase de etiquetado. Su objetivo principal no es proporcionar una interfaz de clase base, sino inyectar algunos typedefs en clases de estilo de functor (a través de la herencia), que los hace utilizables por otras partes de la biblioteca estándar.

+0

Exactamente lo que estaba pensando :) – Akanksh

+2

Sus funciones y plantillas de funciones necesitan tipos de devolución. –

+0

@Charles: como (supongo) que ya sabes, eso es exactamente lo que 'binary_function' proporciona - typedefs para los tipos de los parámetros y la devolución. –

1

Puede haber una solución más genérica, pero en este caso usaría el hecho de que la división es lo mismo que la multiplicación por el inverso, es decir, x/C == x * (1/C).

1

Primero que nada: TransformData debe ser una función de plantilla que usa polimorfismo estático en lugar de dinámico. binary_function es solo "etiqueta", estos múltiplos y divisiones no sobrecargan su operator() - porque no tiene uno.

template<typename Func> 
void TransformData(double *data,Func f) 
{ 
    for(int i=0;i<some_data_size;i++) 
     data[i]=f(data[i],otherdata[i]); 
} 

Y ahora f se usará correctamente. múltiplos, divisiones o cualquier otro.

Y luego se llama a

TransformData(data,std::multiples<double>()); 
TransformData(data,std::divides<double>()); 
TransformData(data,some_other_functional); 
+0

'binary_function' no tiene' operator() ', al menos en la implementación de VC++. –

+0

Has añadido la aclaración – Artyom

0

Como sugirió Marcelo, excepto que no iba a pasar el objeto función como un parámetro, pero construir internamente como:

template <typename TransformationFunctor> 
TransformData(double data) 
{ 
    TransformationFunctor f; 
    ... 
} 

y luego utilizar como:

MultiplyData(double data) 
{ 
    TransformData< std::multiplies<double> >(data); 
} 
+0

¿Cuáles son los beneficios de este enfoque sobre el de Marcelo? –

+1

No creo que esto sea muy diferente de la sugerencia de Marcelo, y cualquiera de los dos funcionaría bien. Prefiero este enfoque ya que reduce la cantidad de argumentos en tiempo de ejecución que se pasan a la función, guarda el "()" para construir un objeto temporal anónimo, y un pase por el valor del objeto (copia de la construcción). Pero para un TranformationFunctor trivial, estos no deberían importar de todos modos. Sin embargo, en general, mantener el número de argumentos de tiempo de ejecución al mínimo es algo bueno, ya que cuando la función se inserta en la pila, los argumentos se pueden colocar en los registros. – Akanksh

+0

Oh, pero hay un retroceso, no se puede "pasar" objetos de función con "estado", así que creo que la implementación de Marcelo es mejor. – Akanksh

3

Es posible que desee ver el uso de Boost.Bind o Boost.Function para que pueda hacer un poco cosa como esta:

TransformData(boost::function<double(double, double)> func) { 
    // use func like a function that 
    // returns a double and takes two 
    // double parameters 
} 
1

me gustaría considerar el uso de std::transform en lugar de su TransformData. Tal como lo ha escrito, TransformData requiere un acoplamiento más ajustado (herencia) de lo que realmente es necesario en este caso. Si usas herencia, significa que tus funciones de multiplicar y dividir deberían ser virtuales, lo que (en el caso de algo tan simple como multiplicación o división) probablemente agregaría una sobrecarga significativa.

std::transform no requiere una llamada de función virtual. Con ella, la función a invocar es un parámetro de plantilla, lo que significa que todo lo que pase admite la sintaxis normal para una llamada de función (es decir, poner paréntesis después del nombre) funcionará.

Desde transform también utiliza iteradores, se puede aplicar directamente a la entrada a medida que lee desde el archivo:

transform(istream_iterator<double>(infile), 
      istream_iterator<double>(), 
      coefficients.begin(), 
      data.begin(), 
      std::multiply); 

// do the other work on data 

transform(data.begin(), 
      data.end(), 
      coefficients.begin(), 
      ostream_iterator<double>(outfile), 
      std::divide); 

Otra posibilidad a considerar sería el uso de std::valarray s en su lugar. Con un valarray, el código podría ser tan simple como: data *= coefficients; y data /= coefficients;

+0

Consideré usar 'transform', pero la multplicación/división es parte de una función más compleja donde la aplicación de la función de entrada está determinada por la salida de las transformaciones previas, es decir, las transformaciones individuales no son independientes entre sí. Lo siento, debería haber dejado esto en claro en lugar de insinuar la naturaleza "más compleja" de la función. Aún así, es un enfoque interesante que tomaría más si no fuera necesario implementar un functor con estado para funcionar. –

Cuestiones relacionadas