Dichiarare una funzione const quando si cambiano i dati dei membri

4

Considera il seguente codice (esempio):

class A
{
private:
    int *_a;
public:
    A() { /* initialize _a to something */ }
    ~A() { /* deallocate _a */ }
    void setA(int i) const
    {
        _a[i] = 3;
    }
};

Questo codice viene compilato e funzionerà come previsto (ad esempio, se chiami setA con qualche input i , imposterà il i th elemento di _a a 3 ). La mia preoccupazione è con il modificatore const collegato alla funzione setA . Non vi è alcun pericolo di errore del compilatore (o peggio, comportamento indefinito) a causa di questo modificatore - la classe A contiene solo un puntatore ai dati che si stanno modificando, non i dati stessi. Detto questo, non posso fare a meno di pensare che usare il modificatore const per una funzione che modifica effettivamente i dati che A è responsabile del mantenimento è sbagliato , in qualche modo. Sono ipersensibile qui, o è davvero una cattiva pratica?

    
posta R_Kapp 27.10.2016 - 20:36
fonte

2 risposte

7

Nel tuo esempio è probabilmente una pessima idea modificare _a[i] .

Avendo detto che mi piacerebbe approfondire un po 'di più:

const è una parola chiave molto utile. Se leggi alcuni libri di Bjarne o di Scott, c'è scritto di usare const il più spesso possibile. Inoltre la modifica dei dati in funzione dichiarata const non è solo possibile, a volte è una buona pratica! Basta ricordare che è necessario prestare attenzione quando si decide se il tuo caso è uno di quei "un po 'di volte. Perché mai sulla Terra metterebbero la parola chiave mutable in C ++ se non dovrebbe essere usato?

Un esempio (da uno dei suddetti autori se ricordo bene) di buon utilizzo di mutable :

Considera poligono di classe:

class Polygon
{
    void calculate_area() { /* we calculate m_area */ }

    std::vector<Vertex> m_vertexes;
    double m_area;

public:
    Polygon(std::initializer_list<Vertex> v_list): m_vertexes(v_list) {}

    double area() const { return m_area; }
    void add_vertex(Vertex v)
    {
        m_vertexes.push_back(v);
        calculate_area();
    }
};

È abbastanza diretto, non è vero? Non vogliamo calcolare l'area ogni volta che viene richiesto di restituirlo, quindi memorizziamo il suo valore in m_area variabile membro e restituiamo questa variabile. Il metodo double area() è const , dopotutto non cambia nulla. Il fatto è che dobbiamo calcolare l'area ogni volta che cambiamo il nostro poligono! Diciamo che aggiungiamo cento vertici uno per uno ... Cento ricalcoli di area! 99 di quelli totalmente inutili. Vogliamo ricalcolare solo se ci viene richiesto di consegnare l'area. Quindi cosa facciamo?

Usiamo mutable !

class Polygon
{
    void calculate_area() const
    {
        /* we calculate m_area */
        m_recalculate_area = false;
    }

    std::vector<Vertex> m_vertexes;
    mutable bool m_recalculate_area = false;
    mutable double m_area = 0.0;

public:
    Polygon(std::initializer_list<Vertex> v_list): m_vertexes(v_list)
    { m_recalculate_area = true; }

    double area() const
    {
        if(m_recalculate_area)
        { calculate_area(); }

        return m_area;
    }

    void add_vertex(Vertex v)
    {
        m_vertexes.push_back(v);
        m_recalculate_area = true;
    }
};

L'effetto è molto carino: per un utente del nostro metodo di classe double area() continua a non modificare l'oggetto su cui sta lavorando, quindi è ancora const . Ma dentro abbiamo guadagnato molto! L'area verrà ricalcolata solo quando ce n'è bisogno. Se nessuno chiede un'area, non verrà calcolato affatto!

Quindi, a quanto ho capito, const dovrebbe indicare che "per quanto riguarda l'utente della classe" il metodo non modifica l'oggetto. L'utente della nostra API di solito non è interessato ai dettagli di implementazione. Se lo è, allora abbiamo la documentazione :).

Ma attenzione: usando mutable perché "sono poche righe di codice in meno" o qualcosa del genere è un grosso errore. Se rendi il tuo metodo const , mantieni la tua parola e usa mutable con la massima cura! Altrimenti, voi e gli utenti del vostro codice presto saranno in grossi problemi.

    
risposta data 27.10.2016 - 22:18
fonte
4

Questa è una cattiva pratica probabilmente .

const dovrebbe normalmente riflettere "costanza logica". Cioè, lo stato logico di un oggetto non dovrebbe essere modificato da una funzione membro const .

Il caso tipico in cui ha senso che una funzione membro const modifichi alcuni degli stati dell'oggetto è quando si esegue una sorta di memorizzazione nella cache.

Se, ad esempio, invece di setA avevi un getResult che calcolava un risultato, poi ne nascondeva il valore in a perché 1) era abbastanza costoso da calcolare e 2) era abbastanza probabile che qualcuno lo riavrei presto, quindi potrebbe / avrebbe probabilmente senso per getResult calcolare il valore e inserirlo nella cache.

Ovviamente modifica il contenuto della cache, ma non modifica il comportamento osservabile dell'oggetto (a parte il dettaglio non secondario di un'operazione eseguita molto più velocemente). In breve, alcuni bit sono stati modificati, ma lo stato logico dell'oggetto non è stato modificato. Per casi come questo, di solito vuoi 1) incapsulare il puntatore in qualcosa di più facile da usare correttamente / più difficile da abusare, e 2) rendere quel wrapper mutabile, per renderlo ovvio.

Dato che è chiamato setA (ed è public ), tuttavia sembra che qualcosa di esterno alla classe possa chiamarlo con l'aspettativa di cambiare effettivamente lo stato dell'oggetto. Se questo è il caso, allora è un caso abbastanza chiaro di scarsa pratica.

    
risposta data 27.10.2016 - 22:21
fonte

Leggi altre domande sui tag