2010-07-15 17 views
11

Considere una clase como ésta:complejos de inicialización de campos const

class MyReferenceClass 
{ 
public: 
    MyReferenceClass(); 
    const double ImportantConstant1; 
    const double ImportantConstant2; 
    const double ImportantConstant3; 
private: 
    void ComputeImportantConstants(double *out_const1, double *out_const2, double *out_const3); 
} 

hay una rutina (ComputeImportantConstants) que calcula tres constantes en tiempo de ejecución. Supongamos que el cálculo es bastante complejo e inherentemente produce los tres valores a la vez. Además, los resultados dependen de la configuración de compilación, por lo que codificar los resultados no es una opción.

¿Existe una forma sensata de almacenar estos valores calculados en los campos dobles const correspondientes de la clase?

Si no, ¿puede sugerir una forma más natural de declarar dicha clase en C++?

En C# Usaría una clase estática con un constructor estático aquí, pero esa no es una opción en C++. También consideré hacer de ImportantConstant1..3 campos no const o llamadas a funciones, pero ambos parecen inferiores.

La única forma de inicializar los campos const que encontré es en use initializer lists, pero no parece posible pasar los resultados de un cálculo de salida múltiple en dicha lista.

+0

Si es posible, ¿puede decir cómo se implementa 'ComputeImportantConstants'? ¿Es bastante largo? ¿Cómo interactúan las tres constantes? ¿Qué otros factores están involucrados? –

Respuesta

9

¿Por qué no se puede hacer:

MyReferenceClass ComputeImportantConstants(){ 
    //stuff to compute 
    return MyReferenceClass(const1, const2, const3); 
} 

MyReferenceClass{ 
public: 
    MyReferenceClass(double _1, double _2, double _3) 
     : m_Const1(_1), 
     m_Const2(_2), 
     m_Const3(_3){} 

    double getImportantConst1() const { return m_Const1; } 
    double getImportantConst2() const { return m_Const2; } 
    double getImportantConst3() const { return m_Const3; } 
private: 
    const double m_Const1, 
       m_Const2, 
       m_Const3; 
}; 

de esa manera y tener la función de calcular su vez en una función de fábrica?

+0

una idea de mejora: crea una variable estática en ComputeImportantConstants() y devuelve esta variable una vez que se haya calculado todo. De esta forma, las llamadas posteriores de ComputeImportantConstants no desencadenan un cálculo adicional. –

+1

C++ nitpick: devolver un 'const double' de una función no tiene mucho sentido. Solo hace la vida más difícil de lo necesario para la persona que llama sin mejorar la seguridad. Las variables miembro se devuelven por valor, después de todo. –

+0

Buen nitpick y tienes razón. – wheaties

5

primero - puedes hacer el mal: descartar const en ComputeImportantConstants() y colocar los valores allí. No lo hagas, porque mentirás al compilador y tratará de encontrar la manera más desagradable de pagar.

en segundo lugar:

hacer algo como esto:

class A 
private: 
    double important1; 
    double important2; 
    double important3; 
    A() { ComputeImportantConstants(); } //no need for parameters, it accesses the members 
    void ComputeImportantConstants(); 
public: 
    inline double GetImportant1() { return important1; } 
    inline double GetImportant2() { return important2; } 
    inline double GetImportant3() { return important3; } 
}; 

todavía se puede mejorar esta clase por lo que es algún tipo de producto único más o menos (ya que desea que el cálculo que se realiza una sola vez).

3

podría mover los const campos a una clase base, y luego pasar a una clase contenedora para inicializar ellos:

class MyBase 
{ 
protected: 
    const double ImportantConstant1; 
    const double ImportantConstant2; 
    const double ImportantConstant3; 

    struct Initializer 
    { 
     double d1; 
     double d2; 
     double d3; 
    }; 

    MyBase(Initializer const& i): 
     ImportantConstant1(i.d1),ImportantConstant2(i.d2),ImportantConstant3(i.d3) 
    {} 
}; 

class MyReferenceClass: 
    private MyBase 
{ 
public: 
    using MyBase::ImportantConstant1; 
    using MyBase::ImportantConstant2; 
    using MyBase::ImportantConstant3; 
    MyReferenceClass(): 
     MyBase(makeInitializer()) 
    {} 

private: 
    MyBase::Initializer makeInitializer() 
    { 
     MyBase::Initializer i; 
     ComputeImportantConstants(&i.d1,&i.d2,&i.d3); 
     return i; 
    } 

    void ComputeImportantConstants(double *out_const1, double *out_const2, double *out_const3); 
}; 
+0

Funciona bien; solo necesitaba cambiar a la herencia pública de MyBase y hacer públicos los campos dobles de const. Además, en el código real, los tres valores ya se han pasado en una estructura, por lo que no necesito una estructura de inicializador adicional. (Realmente debería haber escrito mi pregunta usando esa estructura ...) –

2

La única manera de inicializar los campos const que he encontrado es utilizar listas de inicializador, pero no parece posible pasar los resultados de un cálculo de múltiples salidas en dicha lista.

Eso es cierto; sin embargo, puede inicializar un único miembro, que es una estructura de constantes. Vea abajo.

También he considerado hacer de ImportantConstant1..3 campos no const o llamadas a funciones, pero ambos parecen inferiores.

No creo que las funciones getter sean inferiores. El compilador probablemente los alinearía.Considere esto:

class MyReferenceClass 
{ 
public: 
    MyReferenceClass() : m_constants(ComputeImportantConstants()) { } 

    inline double ImportantConstant1() const { return m_constants.c1; } 
    inline double ImportantConstant2() const { return m_constants.c2; } 
    inline double ImportantConstant3() const { return m_constants.c3; } 

private: 
    struct Constants { 
     Constants(double c1_, double c2_, double c3_) : c1(c1_), c2(c2_), c3(c3_) { } 

     const double c1; 
     const double c2; 
     const double c3; 
    }; 

    Constants ComputeImportantConstants() { 
     return Constants(1.0, 2.0, 3.0); 
    } 

    const Constants m_constants; 
}; 

Desde m_constants, así como todos sus campos son constantes, los valores no se pueden cambiar por otros métodos miembros - sólo en el código que has esbozado en su pregunta. Una inicialización puede ser utilizada aquí ya que inicializamos un único valor: una estructura.

El acceso a las constantes es (más probable) que sea tan eficiente como antes: sugieren sumar las funciones y es bastante probable que el compilador lo haga dado lo pequeños que son los captadores.

1

Sólo dividir la cosa en la parte que es simple para inicializar y la parte compleja, e inicializar la parte compleja a través de copiar constructor:

// here's the part with the consts: 
struct ComplexPart 
{ 
    const double a,b,c; 
    ComplexPart(double _a, double _b, double _c) {} 
}; 
// here's the expensive calc function: 
void calc(double *a,double *b,double *c); 

// and this is a helper which returns an initialized ComplexPart from the computation: 
ComplexPart calc2() 
{ 
    double *a,*b,*c; 
    calc(&a,&b,&b); 
    return ComplexPart(a,b,c); 
} 
// put everything together:  
struct MyReferenceClass : public ComplexPart 
{ 
    MyReferenceClass() : ComplexPart(calc2()) {} 
}; 
+0

El uso de la herencia aquí me parece dolorosamente malo. –

1

¿Qué pasa algo así:

class A 
{ 
    private: 
    static void calc(double &d1, double &d2, double &d3) 
    { 
     d1 = 1.0; 
     d2 = 2.0; 
     d3 = 3.0; 
    } 
    class D 
    { 
     public: 
     operator double() const 
     { 
      return(x); 
     } 
     private: 
     friend class A; 
     double x; 
    }; 
    public: 
    A() 
    { 
     calc(d1.x, d2.x, d3.x); 
    } 
    D d1, d2, d3; 
}; 

#include <iostream> 

int main() 
{ 
    A a; 
    std::cout << a.d1 << std::endl; 
    std::cout << a.d2 << std::endl; 
    std::cout << a.d3 << std::endl; 
    // the following lines will not compile so you can't change the value 
    // std::cout << a.d3.x << std::endl; 
    // a.d2.x = 0.0; 
    return(0); 
} 
1

Ninguna de las respuestas anteriores parece prestar atención a un detalle: static se menciona aquí, por lo que estas constantes parecen ser independientes de la instancia real de la clase.

En otras palabras: esas son constantes globales. Como ya adivinó, la presencia de la palabra clave const es importante aquí, debido a las optimizaciones que aplicará el compilador.

De todos modos, la idea es usar una estructura de ayuda.

// foo.h 
class Foo 
{ 
public: 
    static double const m1; 
    static double const m2; 
    static double const m3; 
}; 

// foo.cpp 
struct Helper 
{ 
    double m1, m2, m3; 
    Helper() { complexInit(m1, m2, m3); } 
} gHelper; 

double const Foo::m1 = gHelper.m1; 
double const Foo::m2 = gHelper.m2; 
double const Foo::m3 = gHelper.m3; 

Por supuesto, en un programa real, les animo a envolver en realidad las constantes detrás de algún tipo de interfaz, que es realmente una mala práctica que se les exponga esta manera, ya que hace que el cambio de ellos (utilizando otro tipo) muy dificil.

También tenga en cuenta que no necesita punteros para los parámetros de salida, las referencias simples sí.

2

Para modificar la respuesta aceptada, tenga en cuenta que a partir de C++ 11 puede hacer trucos muy aseados. Por ejemplo, el problema original se puede resolver con una delegación de lambda y la construcción de la siguiente manera:

class MyReferenceClass { 

public: /* Methods: */ 

    MyReferenceClass() 
     : MyReferenceClass([](){ 
       std::array<double, 3u> cs; /* Helper class, array or tuple */ 
       computeImportantConstants(&cs[0u], &cs[1u], &cs[2u]); 
       return cs; 
      }) 
    {} 

    const double importantConstant1; 
    const double importantConstant2; 
    const double importantConstant3; 

private: /* Methods: */ 

    MyReferenceClass(std::array<double, 3u> constants) 
     : ImportantConstant1(constants[0u]) 
     , ImportantConstant2(constants[1u]) 
     , ImportantConstant3(constants[2u]) 
    {} 

    static void computeImportantConstants(double * out_const1, 
              double * out_const2, 
              double * out_const3); 

}; /* class MyReferenceClass { */ 

O mejor aún, mover el código de inicialización de computeImportantConstants en el lambda en el constructor, si es posible.

En la práctica, el uso de llamadas lambda para inicializar miembros constantes es un truco muy útil, especialmente porque también puede enlazar y/o pasar argumentos a la lambda. Y el uso de la delegación de construcción ayuda a facilitar la inicialización de los miembros que pueden inicializarse mejor o pueden depender el uno del otro.

Sin embargo, tenga precaución al usar delegación de construcción, porque el orden de inicialización de los argumentos de función para una llamada de función (o una llamada de constructor) no está definido, y uno puede terminar inicializando cosas en un orden incorrecto, o de una manera lo que podría ocasionar fugas de recursos si algo falla o arroja una excepción.

Cuestiones relacionadas