2010-07-15 30 views
11

consideri una classe come questa:Complesso di inizializzazione dei campi 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); 
} 

C'è una routine (ComputeImportantConstants) che calcola tre costanti in fase di esecuzione. Supponiamo che il calcolo sia abbastanza complesso e produce intrinsecamente tutti e tre i valori contemporaneamente. Inoltre, i risultati dipendono dalla configurazione della build, quindi i risultati non sono un'opzione.

Esiste un modo ragionevole per archiviare questi valori calcolati nei corrispondenti campi const della classe?

In caso contrario, puoi suggerire un modo più naturale per dichiarare una classe del genere in C++?

In C# vorrei utilizzare una classe statica con un costruttore statico qui, ma questa non è un'opzione in C++. Ho anche considerato di rendere ImportantConstant1..3 campi non-const o chiamate di funzione, ma entrambi sembrano inferiori.

L'unico modo per inizializzare i campi const che ho trovato è use initializer lists, ma non sembra possibile passare i risultati di un calcolo a più uscite in tale elenco.

+0

Se è possibile, puoi dire come viene implementato 'ComputeImportantConstants'? È piuttosto lungo? Come interagiscono le tre costanti, quali altri fattori sono coinvolti? –

risposta

9

Perché non si può fare:

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; 
}; 

così e hanno la funzione di calcolo si trasformano in una funzione di fabbrica?

+0

un'idea di miglioramento: crea una variabile statica in ComputeImportantConstants() e restituisci questa variabile una volta che tutto è stato calcolato. In questo modo, le chiamate successive di ComputeImportantConstants non attivano un calcolo aggiuntivo. –

+1

C++ nitpick: restituire un 'const double' da una funzione non ha molto senso. Rende solo la vita più difficile del necessario per il chiamante senza migliorare la sicurezza. Le variabili membro vengono restituite in base al valore, dopo tutto. –

+0

Buon nitpick e hai ragione. – wheaties

5

prima - puoi fare il male: gettare via const in ComputeImportantConstants() e posizionare i valori lì. Non farlo però, perché poi menti al compilatore e cercherà di trovare il modo più fastidioso per ripagare.

secondo:

fare qualcosa di simile:

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; } 
}; 

è ancora possibile migliorare questa classe rendendolo una sorta di Singleton o giù di lì (da quando si desidera che il calcolo deve essere fatto solo una volta).

3

è possibile spostare i campi const a una classe base, e poi passare un classe wrapper per inizializzare loro:

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

funziona bene; solo bisogno di passare all'ereditarietà di MyBase pubblica e rendere pubblici i due campi const. Inoltre, nel codice reale i tre valori sono già passati in una struttura, quindi non ho bisogno di una struct Initializer extra. (Avrei dovuto davvero scrivere la mia domanda usando quella struttura ...) –

2

L'unico modo per inizializzare i campi const che ho trovato è quello di utilizzare gli elenchi di inizializzazione, ma non sembra possibile passare i risultati di un calcolo a più uscite in tale elenco.

È vero; tuttavia, è possibile inizializzare un singolo membro, che è una struttura di costanti. Vedi sotto.

Ho anche considerato di rendere ImportantConstant1..3 campi non-const o chiamate di funzione, ma entrambi sembrano inferiori.

Non credo che le funzioni getter sarebbero inferiori. Molto probabilmente il compilatore li incorpora.Considerate questo:

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; 
}; 

Dal m_constants così come tutti i suoi campi sono costanti, i valori non possono essere cambiati con altri metodi membri - proprio nel codice che abbozzato nella sua interrogazione. Un'inizializzazione può essere usata qui poiché inizializziamo un singolo valore: una struct.

L'accesso alle costanti è (molto probabilmente) più efficiente di prima: il suggerimento di incorporare le funzioni e il compilatore è molto probabile che lo faccia data la dimensione dei getter.

1

Basta dividere la cosa nella parte che è semplice per inizializzare e la parte complessa, e inizializzare la parte complessa via costruttore di copia:

// 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

L'ereditarietà qui mi sembra dolorosamente sbagliata. –

1

Che dire qualcosa di simile:

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

Nessuna delle risposte precedenti sembrava prestare attenzione a un dettaglio: static è menzionato qui, quindi queste costanti sembrano essere indipendenti dall'istanza reale della classe.

In altre parole: quelle sono costanti globali. Come hai intuito, la presenza della parola chiave const è importante qui, a causa delle ottimizzazioni che il compilatore applicherà.

In ogni caso, l'idea è di utilizzare una struttura di supporto.

// 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; 

Naturalmente, in un programma vero e proprio, vorrei incoraggiarvi ad avvolgere in realtà le costanti dietro qualche tipo di interfaccia, è davvero cattiva pratica di esporre loro in questo modo, perché li fa cambiare (utilizzando un altro tipo) molto difficile.

Si noti inoltre che non sono necessari puntatori per i parametri di output, fare riferimenti semplici.

2

Per modificare la risposta accettata, si noti che a partire dal C++ 11 è possibile eseguire trucchi molto accurati. Ad esempio, il problema originale può essere risolto con una delegazione lambda e la costruzione come segue:

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 meglio ancora, spostare il codice di inizializzazione dal computeImportantConstants nel lambda nel costruttore, se possibile.

In pratica, utilizzare le chiamate lambda per inizializzare membri costanti è un trucco molto utile, soprattutto perché è possibile anche associare e/o passare argomenti al lambda. E l'uso della delega di costruzione aiuta a facilitare l'inizializzazione dei membri che possono essere meglio inizializzati insieme o potrebbero dipendere l'uno dall'altro.

Tuttavia, prestare particolare attenzione quando si utilizza la delega di costruzione, perché l'ordine di inizializzazione degli argomenti di funzione per una chiamata di funzione (o una chiamata del costruttore) non è definito e si potrebbe finire per inizializzare le cose in un ordine errato o in un modo che potrebbe portare a perdite di risorse se qualcosa fallisce o genera un'eccezione.

Problemi correlati