Sök…


Syntax

  • klass 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 & one, const ClassTwo * two) {/ * ... * /}
  • float parameter_modifying_function (ClassTwo & one, ClassOne * two) {/ * ... * /}
  • kort ClassThree :: non_modding_non_param_modding_f (const ClassOne &) const {/ * ... * /}

Anmärkningar

const correctness är ett mycket användbart felsökningsverktyg, eftersom det gör att programmeraren snabbt kan avgöra vilka funktioner som kan oavsiktligt ändra kod. Det förhindrar också oavsiktliga fel, t.ex. det som visas i Const Correct Function Parameters , från att kompilera ordentligt och gå obemärkt.

Det är mycket lättare att utforma en klass för const korrekthet än att senare lägga till const korrekthet till en befintlig klass. Om det är möjligt, utforma en klass som kan vara const korrekt så att den är const korrekt, för att spara dig själv och andra besväret med att senare modifiera den.

Observera att detta också kan tillämpas på volatile korrekthet vid behov med samma regler som för const korrekthet, men detta används mycket mindre ofta.

Hänvisningar:

ISO_CPP

Sälj mig på const correctness

C ++ Tutorial

Det grundläggande

const correctness är praxis att designa kod så att endast kod som behöver modifiera en instans kan modifiera en instans (dvs. har skrivåtkomst), och omvänt, att alla koder som inte behöver modifiera en instans inte kan göra så (dvs. har bara läsåtkomst). Detta förhindrar att instansen ändras oavsiktligt, gör att koden blir mindre felaktig och dokumenterar om koden är avsedd att ändra instansens tillstånd eller inte. Det gör det också möjligt att behandla instanser som const när de inte behöver ändras eller definieras som const om de inte behöver ändras efter initialisering, utan att förlora någon funktion.

Detta görs genom att ge medlemsfunktioner const CV-kvalificeringar och genom att göra pekare / referensparametrar const , förutom om de behöver skrivåtkomst.

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.

På grund av const-korrekthetens natur börjar detta med klassens medlemsfunktioner och fungerar utåt; Om du försöker ringa en icke- const medlemsfunktion från en const instans, eller från en icke- const instans som behandlas som const , kommer kompilatorn att ge dig ett fel om att den tappar cv-kval.

Const Correct Class Design

I en const -korrekt klass har alla medlemsfunktioner som inte ändrar logiskt tillstånd this cv-kvalificerade som const , vilket indikerar att de inte modifierar objektet (bortsett från eventuella mutable fält, som fritt kan ändras även i const instanser ); om en const cv-kvalificerad funktion returnerar en referens, bör referensen också vara const . Detta tillåter att de kallas på både konstanta och icke-cv-kvalificerade instanser, eftersom en const T* kan binda till antingen en T* eller en const T* . Detta tillåter i sin tur funktioner att deklarera sina vidarebefordrade parametrar som const när de inte behöver modifieras utan att förlora någon funktion.

Vidare, i en const korrekt klass, kommer alla passerade referensfunktionsparametrar att vara const korrekta, som diskuteras i Const Correct Function Parameters , så att de endast kan modifieras när funktionen uttryckligen behöver modifiera dem.

Låt oss först titta på this cv-kval:

// 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();
}

Vi kan sedan kombinera detta med Const Correct Function Parameters , vilket gör att klassen är helt 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 {}

Detta kan också kombineras med överbelastning baserad på const , i det fall vi vill ha ett beteende om instansen är const , och ett annat beteende om det inte är det; en vanlig användning för detta är konstanter som tillhandahåller accessorer som endast tillåter modifiering om själva behållaren är icke- 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]; }

    // ...
};

Detta används ofta i standardbiblioteket, med de flesta containrar ger överlast att ta const ness beaktas.

Const Correct Function Parameters

I en const correct-funktion markeras alla förbipasserade parametrar som const inte funktionen ändrar dem direkt eller indirekt, vilket hindrar programmeraren från att oavsiktligt ändra något de inte tänkte ändra. Detta medger att funktionen för att ta både const och icke-cv kvalificerade fall och i sin tur, orsakar instansen är this att vara av typen const T* när en medlem funktion anropas, där T är den klass typ.

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

Även om effekterna av detta är mindre omedelbart synliga än effekterna av const korrekt klassdesign ( const const-correct-funktioner och const correct-klasser orsakar kompilationsfel, medan const correct-klasser och const correct-funktioner kommer att kompilera korrekt), const correct funktioner kommer att fånga en hel del fel som const felaktiga funktioner skulle låta slinka igenom, såsom den nedan. [Notera dock att en const -incorrect funktion kommer att orsaka kompileringsfel om det går igenom en const exempel när det förväntas en icke const en.]

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

En av de mer användbara sakerna med const correctness är att den fungerar som ett sätt att dokumentera kod, vilket ger vissa garantier till programmeraren och andra användare. Dessa garantier verkställs av kompilatorn på grund av const , med en brist på const i sin tur indikerar att koden inte ger dem.

const CV-kvalificerade medlemsfunktioner:

  • Varje medlemsfunktion som är const kan antas ha avsikt att läsa instansen, och:
    • Får inte ändra det logiska tillståndet för den instans de kallas på. Därför får de inte ändra några medlemsvariabler i det instans de kallas för, utom mutable variabler.
    • Får inte ringa några andra funktioner som kan modifiera några medlemsvariabler i instansen, utom mutable variabler.
  • Omvänt kan alla medlemsfunktioner som inte är const antas ha avsikt att ändra förekomsten, och:
    • Kan eller inte ändra logiskt tillstånd.
    • Kan eller kanske inte ringa andra funktioner som ändrar logiskt tillstånd.

Detta kan användas för att göra antaganden om objektets tillstånd efter att en given medlemsfunktion har kallats, även utan att se definitionen av den funktionen:

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

På grund av const regler kommer dessa antaganden faktiskt att verkställas av kompilatorn.

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

const Funktionsparametrar:

  • Alla funktioner med en eller flera parametrar som är const kan antas ha avsikt att läsa dessa parametrar, och:
    • Får inte modifiera dessa parametrar eller ringa några medlemsfunktioner som skulle modifiera dem.
    • Får inte vidarebefordra dessa parametrar till någon annan funktion som skulle modifiera dem och / eller kalla några medlemsfunktioner som skulle modifiera dem.
  • Omvänt någon funktion med en eller flera parametrar som inte är const till kan antas ha för avsikt att ändra dessa parametrar, och:
    • Kan eller inte ändra dessa parametrar, eller ring några medlemsfunktioner som skulle ändra dem.
    • Kan eller inte får överföra dessa parametrar till andra funktioner som skulle modifiera dem och / eller anropa några medlemsfunktioner som skulle modifiera dem.

Detta kan användas för att göra antaganden om tillståndet för parametrarna efter att ha övergått till någon given funktion, även utan att se definitionen av den funktionen.

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

På grund av const regler kommer dessa antaganden faktiskt att verkställas av kompilatorn.

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

Även om det är möjligt att kringgå const korrekthet , och genom förlängning bryta dessa garantier, måste detta göras avsiktligt av programmeraren (precis som att bryta inkapsling med Machiavelli ovan), och kan sannolikt orsaka odefinierat beteende.

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.

På grund av detta som kräver att programmeraren mycket specifikt berättar kompilatorn att de tänker ignorera const och att de är inkonsekventa över kompilatorer är det i allmänhet säkert att anta att const korrekt kod kommer att avstå från att göra det om inte annat anges.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow