Ricerca…


Sintassi

  • class ClassOne {public: bool non_modifying_member_function () const {/ * ... * /}};
  • int ClassTwo :: non_modifying_member_function () const {/ * ... * /}
  • void ClassTwo :: modifying_member_function () {/ * ... * /}
  • char non_param_modding_func (const ClassOne e uno, const ClassTwo * two) {/ * ... * /}
  • float parameter_modifying_function (ClassTwo e uno, ClassOne * two) {/ * ... * /}
  • breve ClassThree :: non_modding_non_param_modding_f (const ClassOne &) const {/ * ... * /}

Osservazioni

const correctness è uno strumento di risoluzione dei problemi molto utile, in quanto consente al programmatore di determinare rapidamente quali funzioni potrebbero inavvertitamente modificare il codice. Evita anche errori non intenzionali, come quello mostrato in Const Correct Function Parameters , dalla compilazione corretta e passando inosservato.

È molto più semplice progettare una classe per la correttezza const , piuttosto che aggiungere successivamente la correttezza const a una classe preesistente. Se possibile, nel Designer classe che può essere const corretta in modo che sia const corretta, per salvare se stessi e gli altri il fastidio di poi modificarlo.

Si noti che questo può anche essere applicato alla correttezza volatile se necessario, con le stesse regole della correttezza const , ma questo viene usato molto meno spesso.

Refrences:

ISO_CPP

Vendimi per correttezza

Tutorial C ++

Le basi

const correttezza const è la pratica della progettazione del codice in modo che solo il codice che ha bisogno di modificare un'istanza sia in grado di modificare un'istanza (cioè ha accesso in scrittura) e, al contrario, qualsiasi codice che non ha bisogno di modificare un'istanza non è in grado di farlo così (cioè ha solo accesso in lettura). Ciò impedisce che l'istanza venga modificata involontariamente, rendendo il codice meno errorprone e documenta se il codice è destinato a modificare lo stato dell'istanza oppure no. Consente inoltre di trattare le istanze come const ogni volta che non è necessario modificarle o definite come const se non devono essere modificate dopo l'inizializzazione, senza perdere alcuna funzionalità.

Questo viene fatto dando funzioni membro const CV-qualificazioni , e rendendo parametri puntatore / riferimento const , salvo il caso che hanno bisogno scrittura.

class ConstCorrectClass {
    int x;

  public:
    int getX() const { return x; } // Function is const: Doesn't modify instance.
    void setX(int i) { x = i; }    // Not const: Modifies instance.
};

// Parameter is const: Doesn't modify parameter.
int const_correct_reader(const ConstCorrectClass& c) {
    return c.getX();
}

// Parameter isn't const: Modifies parameter.
void const_correct_writer(ConstCorrectClass& c) {
    c.setX(42);
}

const ConstCorrectClass invariant; // Instance is const: Can't be modified.
ConstCorrectClass         variant; // Instance isn't const: Can be modified.

// ...

const_correct_reader(invariant); // Good.   Calling non-modifying function on const instance.
const_correct_reader(variant);   // Good.   Calling non-modifying function on modifiable instance.

const_correct_writer(variant);   // Good.   Calling modifying function on modifiable instance.
const_correct_writer(invariant); // Error.  Calling modifying function on const instance.

A causa della natura della correttezza const, questo inizia con le funzioni membro della classe e si avvia verso l'esterno; se provate a chiamare una funzione membro non const da un'istanza const , o da un'istanza non const viene trattata come const , il compilatore vi darà un errore riguardo alla perdita dei qualificatori cv.

Const Correct Class Design

In una classe const -correct, tutte le funzioni membro che non cambiano lo stato logico hanno this cv-qualificato come const , che indica che non modificano l'oggetto (a parte i campi mutable , che possono essere modificati liberamente anche nelle istanze const ); se una funzione const cv-qualificata restituisce un riferimento, tale riferimento dovrebbe essere anche const . Ciò consente loro di essere richiamati su istanze sia costanti che non classificate come cv, in quanto una const T* è in grado di legarsi a un T* oa un const T* . Ciò, a sua volta, consente alle funzioni di dichiarare i propri parametri passati per riferimento come const quando non devono essere modificati, senza perdere alcuna funzionalità.

Inoltre, in una classe const corretta, tutti i parametri della funzione const -by-reference saranno const corretti, come discusso in Const Correct Function Parameters , in modo che possano essere modificati solo quando la funzione ha bisogno di modificarli esplicitamente.

Per prima cosa, diamo un'occhiata a this cv-qualificatore:

// Assume class Field, with member function "void insert_value(int);".

class ConstIncorrect {
    Field fld;

  public:
    ConstIncorrect(Field& f); // Modifies.

    Field& getField();        // Might modify.  Also exposes member as non-const reference,
                              //  allowing indirect modification.
    void setField(Field& f);  // Modifies.

    void doSomething(int i);  // Might modify.
    void doNothing();         // Might modify.
};

ConstIncorrect::ConstIncorrect(Field& f) : fld(f) {} // Modifies.
Field& ConstIncorrect::getField() { return fld; }    // Doesn't modify.
void ConstIncorrect::setField(Field& f) { fld = f; } // Modifies.
void ConstIncorrect::doSomething(int i) {            // Modifies.
    fld.insert_value(i);
}
void ConstIncorrect::doNothing() {}                  // Doesn't modify.


class ConstCorrectCVQ {
    Field fld;

  public:
    ConstCorrectCVQ(Field& f);     // Modifies.

    const Field& getField() const; // Doesn't modify.  Exposes member as const reference,
                                   //  preventing indirect modification.
    void setField(Field& f);       // Modifies.

    void doSomething(int i);       // Modifies.
    void doNothing() const;        // Doesn't modify.
};

ConstCorrectCVQ::ConstCorrectCVQ(Field& f) : fld(f) {}
Field& ConstCorrectCVQ::getField() const { return fld; }
void ConstCorrectCVQ::setField(Field& f) { fld = f; }
void ConstCorrectCVQ::doSomething(int i) {
    fld.insert_value(i);
}
void ConstCorrectCVQ::doNothing() const  {}

// This won't work.
// No member functions can be called on const ConstIncorrect instances.
void const_correct_func(const ConstIncorrect& c) {
    Field f = c.getField();
    c.do_nothing();
}

// But this will.
// getField() and doNothing() can be called on const ConstCorrectCVQ instances.
void const_correct_func(const ConstCorrectCVQ& c) {
    Field f = c.getField();
    c.do_nothing();
}

Possiamo quindi combinare questo con i Const Correct Function Parameters , facendo sì che la classe sia completamente const -correct.

class ConstCorrect {
    Field fld;

  public:
    ConstCorrect(const Field& f);  // Modifies instance.  Doesn't modify parameter.

    const Field& getField() const; // Doesn't modify.  Exposes member as const reference,
                                   //  preventing indirect modification.
    void setField(const Field& f); // Modifies instance.  Doesn't modify parameter.

    void doSomething(int i);       // Modifies.  Doesn't modify parameter (passed by value).
    void doNothing() const;        // Doesn't modify.
};

ConstCorrect::ConstCorrect(const Field& f) : fld(f) {}
Field& ConstCorrect::getField() const { return fld; }
void ConstCorrect::setField(const Field& f) { fld = f; }
void ConstCorrect::doSomething(int i) {
    fld.insert_value(i);
}
void ConstCorrect::doNothing() const {}

Questo può anche essere combinato con sovraccarichi sulla base di const ness, nel caso in cui vogliamo un comportamento se l'istanza è const , e un comportamento diverso se non lo è; un uso comune per questo è costituito da constainer che forniscono accessor che consentono solo modifiche se il contenitore stesso non è const .

class ConstCorrectContainer {
    int arr[5];

  public:
    // Subscript operator provides read access if instance is const, or read/write access
    // otherwise.    
          int& operator[](size_t index)       { return arr[index]; }
    const int& operator[](size_t index) const { return arr[index]; }

    // ...
};

Questo è comunemente usato nella libreria standard, con la maggior parte dei contenitori che fornisce sovraccarichi di prendere const Ness in considerazione.

Const Correggi i parametri delle funzioni

In una funzione const -correct, tutti i parametri passati per riferimento sono contrassegnati come const meno che la funzione li modifichi direttamente o indirettamente, impedendo al programmatore di modificare inavvertitamente qualcosa che non intendevano cambiare. Questo permette la funzione di prendere sia const e istanze non-cv qualificato, e, a sua volta, provoca l'istanza di this sia di tipo const T* quando una funzione membro viene chiamato, dove T è il tipo di classe.

struct Example {
    void func()       { std::cout << 3 << std::endl; }
    void func() const { std::cout << 5 << std::endl; }
};

void const_incorrect_function(Example& one, Example* two) {
    one.func();
    two->func();
}

void const_correct_function(const Example& one, const Example* two) {
    one.func();
    two->func();
}

int main() {
    Example a, b;
    const_incorrect_function(a, &b);
    const_correct_function(a, &b);
}

// Output:
3
3
5
5

Mentre gli effetti di questo sono meno immediatamente visibili di quelli di const design di classe corretto (in quel const funzioni -correct e const classi -incorrect causerà errori di compilazione, mentre const classi -correct e const funzioni -incorrect potranno compilare correttamente), const corretta le funzioni colgono un sacco di errori che le funzioni const errate lascerebbero scivolare attraverso, come quella qui sotto. [Si noti, tuttavia, che un const funzione -incorrect causerà errori di compilazione, se superato un const esempio quando si aspettava un non- const uno.]

// Read value from vector, then compute & return a value.
// Caches return values for speed.
template<typename T>
const T& bad_func(std::vector<T>& v, Helper<T>& h) {
    // Cache values, for future use.
    // Once a return value has been calculated, it's cached & its index is registered.
    static std::vector<T> vals = {};

    int v_ind = h.get_index();               // Current working index for v.
    int vals_ind = h.get_cache_index(v_ind); // Will be -1 if cache index isn't registered.

    if (vals.size() && (vals_ind != -1) && (vals_ind < vals.size()) && !(h.needs_recalc())) {
        return vals[h.get_cache_index(v_ind)];
    }

    T temp = v[v_ind];

    temp -= h.poll_device();
    temp *= h.obtain_random();
    temp += h.do_tedious_calculation(temp, v[h.get_last_handled_index()]);

    // We're feeling tired all of a sudden, and this happens.
    if (vals_ind != -1) {
        vals[vals_ind] = temp;
    } else {
        v.push_back(temp);  // Oops.  Should've been accessing vals.
        vals_ind = vals.size() - 1;
        h.register_index(v_ind, vals_ind);
    }

    return vals[vals_ind];
}

// Const correct version.  Is identical to above version, so most of it shall be skipped.
template<typename T>
const T& good_func(const std::vector<T>& v, Helper<T>& h) {
    // ...

    // We're feeling tired all of a sudden, and this happens.
    if (vals_ind != -1) {
        vals[vals_ind] = temp;
    } else {
        v.push_back(temp);  // Error: discards qualifiers.
        vals_ind = vals.size() - 1;
        h.register_index(v_ind, vals_ind);
    }

    return vals[vals_ind];
}

Const Correctness come documentazione

Una delle cose più utili della correttezza const è che serve come un modo di documentare il codice, fornendo certe garanzie al programmatore e agli altri utenti. Tali garanzie vengono applicate dal compilatore causa const ness, con una mancanza di const Ness a sua volta indicando che il codice non fornisce loro.

Funzioni membro qualificate CV const :

  • Si può presumere che ogni funzione membro che è const abbia intenzione di leggere l'istanza e:
    • Non modifica lo stato logico dell'istanza su cui sono chiamati. Pertanto, non devono modificare alcuna variabile membro dell'istanza su cui sono chiamati, tranne le variabili mutable .
    • Non chiama alcuna altra funzione che possa modificare qualsiasi variabile membro dell'istanza, tranne le variabili mutable .
  • Viceversa, si può presumere che qualsiasi funzione membro che non è const abbia intenzione di modificare l'istanza e:
    • Può o non può modificare lo stato logico.
    • Può o non può chiamare altre funzioni che modificano lo stato logico.

Questo può essere usato per fare assunzioni sullo stato dell'oggetto dopo che ogni funzione membro è stata chiamata, anche senza vedere la definizione di quella funzione:

// ConstMemberFunctions.h

class ConstMemberFunctions {
    int val;
    mutable int cache;
    mutable bool state_changed;

  public:
    // Constructor clearly changes logical state.  No assumptions necessary.
    ConstMemberFunctions(int v = 0);

    // We can assume this function doesn't change logical state, and doesn't call
    //  set_val().  It may or may not call squared_calc() or bad_func().
    int calc() const;

    // We can assume this function doesn't change logical state, and doesn't call
    //  set_val().  It may or may not call calc() or bad_func().
    int squared_calc() const;

    // We can assume this function doesn't change logical state, and doesn't call
    //  set_val().  It may or may not call calc() or squared_calc().
    void bad_func() const;

    // We can assume this function changes logical state, and may or may not call
    //  calc(), squared_calc(), or bad_func().
    void set_val(int v);
};

A causa delle regole const , queste ipotesi saranno effettivamente applicate dal compilatore.

// ConstMemberFunctions.cpp

ConstMemberFunctions::ConstMemberFunctions(int v /* = 0*/)
  : cache(0), val(v), state_changed(true) {}

// Our assumption was correct.
int ConstMemberFunctions::calc() const {
    if (state_changed) {
        cache = 3 * val;
        state_changed = false;
    }

    return cache;
}

// Our assumption was correct.
int ConstMemberFunctions::squared_calc() const {
    return calc() * calc();
}

// Our assumption was incorrect.
// Function fails to compile, due to `this` losing qualifiers.
void ConstMemberFunctions::bad_func() const {
    set_val(863);
}

// Our assumption was correct.
void ConstMemberFunctions::set_val(int v) {
    if (v != val) {
        val = v;
        state_changed = true;
    }
}

Parametri funzione const :

  • Qualsiasi funzione con uno o più parametri che sono const può avere l'intento di leggere quei parametri, e:
    • Non modifica questi parametri o chiama le funzioni membro che potrebbero modificarli.
    • Non passa quei parametri a nessuna altra funzione che li modifichi e / o chiami qualsiasi funzione membro che li modifichi.
  • Viceversa, si può presumere che qualsiasi funzione con uno o più parametri che non sono const abbia intenzione di modificare tali parametri, e:
    • Può o non può modificare quei parametri, o chiamare qualsiasi funzione membro che li possa modificare.
    • Può o non può passare quei parametri ad altre funzioni che potrebbero modificarli e / o chiamare qualsiasi funzione membro che li modifichi.

Questo può essere usato per fare assunzioni sullo stato dei parametri dopo essere passati a una data funzione, anche senza vedere la definizione di quella funzione.

// function_parameter.h

// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
//  to non_qualified_function_parameter().  If passed to one_const_one_not(), it is the first
//  parameter.
void const_function_parameter(const ConstMemberFunctions& c);

// We can assume that c is modified and/or c.set_val() is called, and may or may not be passed
//  to any of these functions.  If passed to one_const_one_not, it may be either parameter.
void non_qualified_function_parameter(ConstMemberFunctions& c);

// We can assume that:
  // l is not modified, and l.set_val() won't be called.
  // l may or may not be passed to const_function_parameter().
  // r is modified, and/or r.set_val() may be called.
  // r may or may not be passed to either of the preceding functions.
void one_const_one_not(const ConstMemberFunctions& l, ConstMemberFunctions& r);

// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
//  to non_qualified_function_parameter().  If passed to one_const_one_not(), it is the first
//  parameter.
void bad_parameter(const ConstMemberFunctions& c);

A causa delle regole const , queste ipotesi saranno effettivamente applicate dal compilatore.

// function_parameter.cpp

// Our assumption was correct.
void const_function_parameter(const ConstMemberFunctions& c) {
    std::cout << "With the current value, the output is: " << c.calc() << '\n'
              << "If squared, it's: " << c.squared_calc()
              << std::endl;
}

// Our assumption was correct.
void non_qualified_function_parameter(ConstMemberFunctions& c) {
    c.set_val(42);
    std::cout << "For the value 42, the output is: " << c.calc() << '\n'
              << "If squared, it's: " << c.squared_calc()
              << std::endl;
}

// Our assumption was correct, in the ugliest possible way.
// Note that const correctness doesn't prevent encapsulation from intentionally being broken,
//  it merely prevents code from having write access when it doesn't need it.
void one_const_one_not(const ConstMemberFunctions& l, ConstMemberFunctions& r) {
    // Let's just punch access modifiers and common sense in the face here.
    struct Machiavelli {
        int val;
        int unimportant;
        bool state_changed;
    };
    reinterpret_cast<Machiavelli&>(r).val = l.calc();
    reinterpret_cast<Machiavelli&>(r).state_changed = true;

    const_function_parameter(l);
    const_function_parameter(r);
}

// Our assumption was incorrect.
// Function fails to compile, due to `this` losing qualifiers in c.set_val().
void bad_parameter(const ConstMemberFunctions& c) {
    c.set_val(18);
}

Mentre è possibile aggirare la correttezza const , e per estensione rompere queste garanzie, questo deve essere fatto intenzionalmente dal programmatore (proprio come interrompere l'incapsulamento con Machiavelli , sopra), ed è probabile che causi un comportamento indefinito.

class DealBreaker : public ConstMemberFunctions {
  public:
    DealBreaker(int v = 0);

    // A foreboding name, but it's const...
    void no_guarantees() const;
}

DealBreaker::DealBreaker(int v /* = 0 */) : ConstMemberFunctions(v) {}

// Our assumption was incorrect.
// const_cast removes const-ness, making the compiler think we know what we're doing.
void DealBreaker::no_guarantees() const {
    const_cast<DealBreaker*>(this)->set_val(823);
}

// ...

const DealBreaker d(50);
d.no_guarantees(); // Undefined behaviour: d really IS const, it may or may not be modified.

Tuttavia, a causa di questo che richiede al programmatore di dire in modo molto specifico al compilatore che intendono ignorare const ness, e di essere incoerente nei compilatori, è generalmente lecito ritenere che const codice corretto si asterrà dal farlo se non diversamente specificato.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow