Dovrei usare const più in C ++?

6

Ci sono alcune cose su cui sono piuttosto rigido, ma const non è stato uno di questi.

Ad esempio, potrei creare una variabile locale che utilizzo tre volte in una funzione, che non viene modificata, eppure non mi preoccupo di renderla const.

È qualcosa su cui dovrei essere più severo? Ci sono dei benefici tangibili di cui dovrei essere a conoscenza? È qualcosa che potresti segnalare se stavi rivedendo il mio codice?

    
posta Akiva 02.11.2016 - 01:13
fonte

5 risposte

5

Is this something that I should be more strict about? Are there any tangible benefits that I should be aware of?

Come sottolinea @JerryHeremiah, dopo un certo punto non aggiunge nulla per l'ottimizzazione del compilatore, ma può migliorare la leggibilità.

Tendo a farlo (usa const quando possibile, per una maggiore leggibilità), perché si adatta alla legge di Demeter ("se questo valore non ha bisogno di essere modificato, quindi non permettere che sia, oltre questo punto ") ed esprime meglio l'intento dell'autore (in un modo che il compilatore verifica).

Is this something you would flag if you were reviewing my code?

Dipende dai criteri del codice di revisione concordato per un progetto (sì, è qualcosa che vorrei segnalare, se questa fosse una condizione da segnalare per gli standard di codifica del progetto , e io sosterrebbe anche che questo dovrebbe essere una condizione da segnalare per un progetto).

    
risposta data 07.11.2016 - 10:28
fonte
4

Make interfaces easy to use correctly and hard to use incorrectly

const ti consente di fare proprio questo. Se la funzione restituisce un insieme di elementi che il chiamante della funzione non può modificare, non fare affidamento sul chiamante che legge la documentazione, basta restituire const elementi, risp const_iterator . Non solo è meno probabile che il chiamante commetta un errore, utilizzando const è anche molto meno digitante rispetto a scrivere un commento che dice che il chiamante non dovrebbe modificare gli elementi.

Il "problema" con const è, che si propaga. Se il tuo codice non è corretto, lo stai rendendo molto più difficile per il codice che utilizza il tuo codice per essere corretto. Quindi se non scrivi const interfacce pubbliche corrette, devi forzare le persone utilizzare entrambi hack fragili, o per unirti a te nel non scrivere il codice corretto di const, che si traduce in molte violazioni di tale citazione nella parte superiore della mia risposta.

Al di fuori delle interfacce accessibili al pubblico, ci sono in effetti rendimenti decrescenti, perché non devi preoccuparti tanto della propagazione const. Tuttavia, in questi casi, considera const come una forma di documentazione, che è facile da leggere e scrivere. Se il codice è sufficientemente banale, quella documentazione non è semplicemente necessaria, ma nella maggior parte se non in tutti i casi è molto più facile scrivere const di quanto non sia per capire se il codice è abbastanza banale da ometterlo.

    
risposta data 07.11.2016 - 19:37
fonte
3

Direi che qualsiasi variabile locale che non cambia dovrebbe essere constexpr se possibile e fare riferimento a const se non lo è. Se intendi modificare una variabile devi veramente sapere se si tratta di un riferimento o di una copia locale. Se ti sbagli, il tuo programma non farà ciò che ti aspetti che faccia. Tuttavia se la variabile è const non importa se si tratta di un riferimento o di una copia. Il linguaggio supporta ciò consentendo a un const ref di collegarsi temporaneamente.

// some functions
int& by_ref();
int by_val();

int main(){
  {
    auto a = by_ref();  // takes a copy
    const auto b = by_ref();  // also takes a copy
    auto& c = by_ref();  // no copy
    const auto& d = by_ref();  // no copy 
  }
  {
    auto a = by_val();  // might copy
    const auto b = by_val();  // might copy
    auto& c = by_val();  // error reference to temporary
    const auto& d = by_val();  // allowed! no copy.
  }
}

Si noti che a, be c si comportano in modo diverso a seconda che l'assegnazione sia per riferimento o per valore. Ma d è lo stesso in entrambi i casi.
Se una funzione restituisce un riferimento, d si limiterà a fare riferimento a quel valore, se restituisce un valore, quindi d si riferirà a un oggetto temporaneo, la durata di tale oggetto verrà estesa fino a quando d non sarà più disponibile.

Quindi usando const auto & laddove possibile il tuo codice è più robusto alle modifiche nel resto del codice. È possibile modificare il valore restituito dal riferimento al valore senza rompere il codice e dal valore al riferimento senza incorrere in una copia aggiuntiva.

Se costruisci un'istanza di una classe nella tua funzione allora usare un riferimento const è un po 'strano e potrebbe confondere qualcuno che lo sta leggendo. Per un tipo integrato è già stato sottolineato che probabilmente non c'è alcun vantaggio in termini di prestazioni per la marcatura di const, ma per un tipo più complesso ci sono buone ragioni per farlo. Considera quanto segue.

class my_class{

protected:
    struct my_data {
      int x, y, z;
    };
    ~my_class() = default;

private:
    virtual my_data& data() = 0;
    virtual const my_data& data() const = 0;

public:
    auto& x() {
      return data().x;
    }
    auto& y() {
      return data().y;
    }
    auto& z() {
      return data().z;
    }

    auto& x() const{
      return data().x;
    }
    auto& y() const{
      return data().y;
    }
    auto& z() const{
      return data().z;
    }
};

Noterai che ho definito tutto in my_class due volte. Sono un tipo pigro, quindi non ho intenzione di farlo senza una buona ragione. Il motivo è che un'istanza const di my_class può accedere solo ai metodi etichettati const. Quindi ho bisogno di fornire sia le versioni const che non const di alcune funzioni e il mio istinto naturale è di chiedermi se ho davvero bisogno di fare tutto ciò che digita.

È abbastanza ovvio per questa versione che abbiamo bisogno delle versioni const di tutte le funzioni. È chiaramente un'astrazione e non posso sapere come verrà implementata o utilizzata. È ragionevole presumere che qualcuno vorrà un const my_class da qualche parte e dovrebbero comunque essere autorizzati a utilizzare tutti i metodi.

Tuttavia, come di solito preferisco astrarre attraverso i template, a volte ho una piccola classe che userò solo localmente o come soluzione per qualche strano sintassi, quindi può essere davvero allettante fornire solo il non versione const di una funzione e utilizzare solo istanze non const.

Che funziona benissimo finché non smette di funzionare. Quando ad un certo punto da qualche parte una versione const di qualcosa crea un'istanza di una versione const della mia classe otteniamo uno di quei messaggi di errore stile guerra e pace che i meta-programmatori template amano così tanto.

Quindi la morale è sempre definire le versioni const e non const delle funzioni membro. I motivi sono esattamente gli stessi dell'esempio my_class, ma non sempre sono del tutto ovvi in ogni contesto. Sfortunatamente se non è ovvio il motivo per cui dovresti fare qualcosa, di solito non è ovvio che non farlo sia ciò che ha causato un errore o un bug.

Quindi, se dichiarate sempre variabili come const a meno che non sia necessario modificarle catturerai qualsiasi classe che non ha versioni const delle sue funzioni. Ciò significa che sarai meno riluttante a metterli in primo piano perché sai che capirai l'errore abbastanza presto da doverlo risolvere da solo.

Anche se non hai definito la classe tu stesso, potrebbero esserci versioni leggermente diverse di alcuni metodi per const e non const.

In genere non dovrebbe essere il caso che le funzioni di membro const e non const di una classe sono drammaticamente diverse ma non c'è nulla nella lingua che dice che devono essere uguali. Quindi dichiarando una variabile locale const si potrebbe avere un comportamento leggermente diverso ed è probabilmente il comportamento const che si desidera. L'unico esempio a cui riesco a pensare dove potrebbe incorrere in un colpo di performance è in una situazione multithread. L'accesso a una funzione membro non const potrebbe implicare l'adozione di un blocco di scrittura che dovrebbe attendere l'uscita di tutti i lettori, ma la versione const potrebbe richiedere solo l'arrivo di uno scrittore.

Quindi ci sono buone ragioni per preferire const in molte situazioni, sebbene per i tipi integrati e alcune piccole classi gli argomenti non sono altrettanto forti. In questo caso, in questo caso, raccomanderei di applicare la regola di non-ottimizzazione preferendo sempre const piuttosto che fare alcune eccezioni senza grandi vantaggi.

    
risposta data 07.11.2016 - 18:06
fonte
2

Per le variabili primitive locali, penso che la costanza sia molto utile come una scorciatoia quando si traccia una funzione. Mi comunica immediatamente " questo non cambia mai ", e se stai tentando di eseguire il debug di un problema associato a cambiamenti di stato, tutto definito come costante ti consente di ometterlo rapidamente dalla tua lista di sospetti. Non l'ho mai trovato tanto utile per la programmazione difensiva quanto piuttosto per comunicare in modo sintetico le intenzioni del programmatore.

Quando eseguiamo il debug del codice, di solito passiamo la maggior parte del nostro tempo a esaminare gli stati e il modo in cui cambiano nel corso di ogni riga di codice. const dice immediatamente, "Non devi sorvegliarmi, non cambierò" e quindi lo trovo incredibilmente prezioso e ti consiglio di iniziare a utilizzare const più spesso per i locali che non hanno bisogno di cambiare.

Dove penso che C e C ++ abbiano incasinato era come la costanza interagisce con i tipi definiti dall'utente. Se hai qualcosa di simile:

// Does this cause side effects?
void do_something(const iterator first, const iterator last);

... l'algoritmo modifica l'intervallo o no? Questa non è una domanda a cui viene risposto constness qui, perché const modifica solo iterator per impedirgli di modificare il puntatore , non il pointee . È anche abbastanza facile copiare first e last in copie in cui sia il puntatore sia il pointee possono essere modificati. Di conseguenza, la natura della lingua richiede un intero tipo const_iterator separato per impedire che pointee venga modificato:

// This probably doesn't cause side effects.
void do_something(const_iterator first, const_iterator last);

Stesso caso con:

// Some kind of handle.
class Foo
{
public:
    ...

    // Does this cause side effects?
    void some_func() const;

private:
    Bar* ptr;
};

Stesso scenario di const iterator . Poiché Foo memorizza un puntatore a Bar , contrassegnando some_func come read-only non impedisce di modificare il contenuto di Bar , il pointee. Impedisce solo di modificare il puntatore.

L'idea che const si applichi ai puntatori piuttosto che ai pointees per i membri UDT sembra incredibilmente miope, perché in genere se ci sono degli effetti collaterali dovremmo preoccuparci , è rispetto al pointee , non al puntatore. Una funzione che accetta const iterator non può causare effetti secondari attraverso quel parametro, non importa ciò che fa al puntatore, purché non tocchi il pointee, tuttavia constness si applica al contrario.

La vera questione di interesse quando le funzioni sono coinvolte è tipicamente se causano o meno effetti collaterali, e const non è mai stato sufficiente in questi due linguaggi per esprimere e comunicare chiaramente una chiara indicazione di ciò. Vorrei che questo problema potesse essere affrontato, magari con una nuova parola chiave che impedisca alle funzioni di invocare qualsiasi effetto collaterale di qualsiasi genere al di fuori del proprio ambito (non essendo nemmeno in grado di chiamare altre funzioni che causano effetti collaterali e non in grado di definire la funzione locale variabili statiche). Quello sarebbe uno dei miei sogni di sicurezza. Fino ad allora, ho smesso di preoccuparmi di cost-correct così tanto al livello UDT fino al punto in cui sto cercando di creare due classi diverse (una sola classe di sola lettura, una classe mutevole) per tutto, perché trovo che generalmente costa più tempo di quanto risparmia.

    
risposta data 10.12.2017 - 19:29
fonte
-2

Is this something that I should be more strict about? Are there any tangible benefits that I should be aware of? Is this something you would flag if you were reviewing my code?

No. Al contrario, const è in generale una completa perdita di tempo che non porta a nulla di più che contrassegnare ossessivamente tutto come const nonostante il fatto che sia chiaramente non mutante. L'utilizzo di const in cui non è necessario causerà solo un dolore infinito, rendendo tutto const e duplicando ogni sovraccarico e tale assurdità.

const è giustificato in es. imposta le chiavi, ma oltre a questo, è una completa perdita di tempo.

    
risposta data 07.11.2016 - 19:25
fonte