2011-01-25 16 views
10

Comencé una migración de un algoritmo de física de alta energía escrito en FORTRAN a un enfoque orientado a objetos en C++. El código FORTRAN utiliza muchas variables globales en una gran cantidad de funciones.Patrón para compartir datos entre objetos en C++

He simplificado las variables globales en un conjunto de variables de entrada, y un conjunto de invariantes (variables calculadas una vez al comienzo del algoritmo y luego utilizadas por todas las funciones).

Además, he dividido el algoritmo completo en tres pasos lógicos, representados por tres clases diferentes. Por lo tanto, de una manera muy sencilla, tengo algo como esto:

double calculateFactor(double x, double y, double z) 
{ 
    InvariantsTypeA invA(); 
    InvariantsTypeB invB(); 

    // they need x, y and z 
    invA.CalculateValues(); 
    invB.CalculateValues(); 

    Step1 s1(); 
    Step2 s2(); 
    Step3 s3(); 

    // they need x, y, z, invA and invB 
    return s1.Eval() + s2.Eval() + s3.Eval(); 
} 

Mi problema es:

  • para hacer los cálculos de todos los InvariantsTypeX y StepX objetos necesitan los parámetros de entrada (y estos no son solo tres).
  • los tres objetos s1, s2 y s3 necesitan los datos de los objetos invA y invB.
  • todas las clases utilizan varias otras clases a través de la composición para hacer su trabajo, y todas esas clases también necesitan la entrada y los invariantes (por ejemplo, s1 tiene un objeto miembro theta de clase ThetaMatrix que necesita x, z y invB para construirse).
  • No puedo reescribir el algoritmo para reducir los valores globales, porque sigue varias fórmulas de física de alta energía, y esas fórmulas son así.

¿Hay un buen patrón para compartir los parámetros de entrada y los invariantes a todos los objetos que se utilizan para calcular el resultado?

¿Debo usar singletons? (pero la función calculateFactor se evalúa alrededor de un millón de veces)

¿O debería pasar todos los datos requeridos como argumentos a los objetos cuando se crean? (pero si hago eso, entonces los datos se pasarán a todas partes en cada miembro objeto de cada clase, creando un lío)

Gracias.

+6

"¿Debo usar singletons?" NOOOOOOOOOOO! (Gritando de forma muy melodramática, en cámara lenta :)) –

+1

Para aclarar lo que dice @John: los singleton son una herramienta útil, pero no son la herramienta adecuada para esta situación. No estoy completamente convencido de que necesite usar clases aquí, ya que este es el dominio de la programación funcional, pero no es la peor solución posible al problema. –

+0

¿FORTRAN ha "nombrado bloques COMMON"? –

Respuesta

2

Bueno, en C++ la solución más adecuada, dadas sus limitaciones y condiciones, se representa mediante punteros. Muchos desarrolladores le dijeron que use boost :: shared_ptr. Bueno, no es necesario, aunque proporciona un mejor rendimiento, especialmente cuando se considera la portabilidad y la solidez de las fallas del sistema.

No es necesario que se una al impulso. Es cierto que no están compilados y que ahora los procesos de estandarización llevarán a C++ con boost integrado directamente como una biblioteca estándar, pero si no quieres usar una biblioteca externa, obviamente puedes.

Así que vamos a tratar de resolver su problema utilizando solo C++ y lo que realmente proporciona.

Probablemente tengas un método principal y allí, ya dijiste antes, inicializaste todos los elementos invariantes ... así que básicamente tienes constantes y pueden ser de todos los tipos posibles. no es necesario que sean constantes si lo desea, sin embargo, en la instancia principal instancia sus elementos invariables y los señala para todos los componentes que requieren su uso. Por primera vez en un archivo separado llamado "common_components.hpp" considerar lo siguiente (supongo que es necesario para algunos tipos variables invariantes):

typedef struct { 
    Type1 invariant_var1; 
    Type2 invariant_var2; 
    ... 
    TypeN invariant_varN; 
} InvariantType; // Contains the variables I need, it is a type, instantiating it will generate a set of global variables. 
typedef InvariantType* InvariantPtr; // Will point to a set of invariants 

en su archivo "main.cpp" tendrá:

#include "common_components.hpp" 
// Functions declaration 
int main(int, char**); 
MyType1 CalculateValues1(InvariantPtr); /* Your functions have as imput param the pointer to globals */ 
MyType2 CalculateValues2(InvariantPtr); /* Your functions have as imput param the pointer to globals */ 
... 
MyType3 CalculateValuesN(InvariantPtr); /* Your functions have as imput param the pointer to globals */ 
// Main implementation 
int main(int argc, char** argv) { 
    InvariantType invariants = { 
     value1, 
     value2, 
     ... 
     valueN 
    }; // Instantiating all invariants I need. 
    InvariantPtr global = &invariants; 
    // Now I have my variable global being a pointer to global. 
    // Here I have to call the functions 
    CalculateValue1(global); 
    CalculateValue2(global); 
    ... 
    CalculateValueN(global); 
} 

Si tiene funciones que vuelven o usan la variable global, use el puntero a la estructura que modifica la interfaz de los métodos. Al hacerlo, todos los cambios se verán inundados a todos usando las variables thoss.

2

¿Por qué no pasar las invariantes como un parámetro de función o al constructor de la clase que tiene el método calculateFactor?

También intente juntar parámetros si tiene demasiados parámetros para una sola función (por ejemplo, en lugar de (x, y, z) pasar un punto 3D, entonces tiene solo 1 parámetro en lugar de 3).

2

Hay una clase de plantilla muy simple para compartir datos entre objetos en C++ y se llama shared_ptr. Está en el nuevo STL y en boost.

Si dos objetos tienen un shared_ptr para el mismo objeto, obtienen acceso compartido a cualquier dato que contenga.

En su caso particular, probablemente no desee esto, pero quiere una clase simple que contenga los datos.

class FactorCalculator 
{ 
    InvariantsType invA; 
    InvariantsType invB; 

public: 
    FactorCalculator() // calculate the invariants once per calculator 
    { 
     invA.CalculateValues(); 
     invB.CalculateValues(); 
    } 

    // call multiple times with different values of x, y, z 
    double calculateFactor(double x, double y, double z) /*const*/ 
    { 
     // calculate using pre-calculated values in invA and invB 
    } 
}; 
2

tres pasos lógicos, representados por tres clases diferentes

esto puede no haber sido el mejor enfoque.

Una sola clase puede tener una gran cantidad de variables "globales", compartidas por todos los métodos de la clase.

Lo que he hecho al convertir códigos antiguos (C o Fortran) a nuevas estructuras OO es intentar crear una sola clase que representa una "cosa" más completa.

En algunos casos, FORTRAN bien estructurado usaría "Bloques COMUNES con nombre" para agrupar las cosas en grupos significativos. Esto es una pista de lo que realmente era la "cosa".

Además, FORTRAN tendrá muchas matrices paralelas que no son cosas separadas, son atributos separados de algo común.

DOUBLE X(200) 
DOUBLE Y(200) 

Es realmente una clase pequeña con dos atributos que pondría en una colección.

Finalmente, puede crear fácilmente clases grandes con nada más que datos, separados de la clase que contiene las funciones que hacen el trabajo. Esto es algo espeluznante, pero le permite solucionar el problema común traduciendo un bloque COMMON en una clase y simplemente pasando una instancia de esa clase a cada función que usa COMMON.

+0

De hecho, si el algoritmo es un paso en tres pasos que necesita todos los mismos datos, use una clase. No es necesario separar todo en el olvido. – rubenvb

0

En lugar de pasar cada parámetro individualmente, crear otra clase para almacenar todos ellos y pasar una instancia de esa clase:

// Before 
void f1(int a, int b, int c) { 
    cout << a << b << c << endl; 
} 

// After 
void f2(const HighEnergyParams& x) { 
    cout << x.a << x.b << x.c << endl; 
} 
+0

Lo estoy haciendo, pero quería mostrar el problema original para ver si alguien da otra solución. –

0

Primer punto: variables globales no son casi tan malo (en sí mismos) como muchos (¿la mayoría?) afirman los programadores. De hecho, en sí mismos, no son realmente malos en absoluto. Principalmente son un síntoma de otros problemas, principalmente 1) piezas de código lógicamente separadas que se han entremezclado innecesariamente, y 2) código que tiene dependencias de datos innecesarias.

En su caso, suena como ya eliminado (o al menos minimizado) los problemas reales (siendo invariantes, en realidad no variables, elimina una fuente importante de problemas por sí mismo). Ya ha declarado que no puede eliminar las dependencias de datos, y aparentemente ha desmezclado el código hasta el punto de que tiene al menos dos conjuntos distintos de invariantes. Sin ver el código, puede ser una granularidad más gruesa de lo que realmente se necesita, y tal vez tras una inspección más cercana, algunas de esas dependencias pueden eliminarse por completo.

Si puede reducir o eliminar las dependencias, ese es un objetivo que vale la pena, pero eliminar los elementos globales, en sí mismo, rara vez vale la pena o es útil. De hecho, diría que en la última década, he visto menos problemas causados ​​por los globales, que por personas que realmente no entendieron sus problemas tratando de eliminar lo que eran (o deberían haber sido) perfectamente bien como los globales .

Dado que están destinados a ser invariables, lo que probablemente debería hacer es hacer cumplir eso explícitamente. Por ejemplo, tenga una clase (o función) de fábrica que cree una clase invariante. La clase invariante hace que la fábrica sea su amiga, pero esa es la forma solo que los miembros de la clase invariante pueden cambiar.La clase de fábrica, a su vez, tiene (por ejemplo) un bool estático, y ejecuta un assert si intenta ejecutarlo más de una vez. Esto proporciona (un nivel razonable de) seguridad de que los invariantes son realmente invariables (sí, un reinterpret_cast le permitirá modificar los datos de todos modos, pero no por accidente).

La única pregunta real que tengo es si hay un punto real en separar tus invariantes en dos "pedazos" si todos los cálculos realmente dependen de ambos. Si hay una separación clara y lógica entre los dos, es genial (incluso si se usan juntos). Sin embargo, si tiene lo que lógicamente es un solo bloque de datos, intentar romperlo en pedazos puede ser contraproducente.

Conclusión: los niveles globales son (en el peor de los casos) un síntoma, no una enfermedad. Insistir en que es en para bajar la temperatura del paciente a 98.6 grados puede ser contraproducente, especialmente si el paciente es un animal cuya temperatura corporal normal es de 102 grados.

Cuestiones relacionadas