Suche…


Syntax

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

Bemerkungen

const Korrektheit ist ein sehr hilfreiches Werkzeug zur Fehlerbehebung, da der Programmierer schnell feststellen kann, welche Funktionen versehentlich Code ändern. Außerdem wird verhindert, dass ungewollte Fehler, wie z. B. die in Const Correct Function Parameters gezeigten, ordnungsgemäß kompiliert werden und unbemerkt bleiben.

Es ist viel einfacher, eine Klasse für const Korrektheit zu entwerfen, als später einer vorhandenen Klasse die const Korrektheit hinzuzufügen. Wenn möglich, entwerfen jede Klasse , die sein kann const korrekt , so dass es const richtig, sich selbst und andere die Mühe davon später modifiziert zu speichern.

Beachten Sie, dass dies bei Bedarf auch auf die volatile Korrektheit angewendet werden kann, mit denselben Regeln wie für die Korrektheit von const Diese wird jedoch viel seltener verwendet.

Referenzen:

ISO_CPP

Verkaufen Sie mich aufrichtig

C ++ Tutorial

Die Grundlagen

const Korrektheit ist die Praxis Code so zu gestalten, dass nur Code, der eine Instanz ändern muss in der Lage ist , eine Instanz zu modifizieren (dh hat Schreibzugriff), und umgekehrt, dass jeder Code, der braucht nicht eine Instanz zu ändern , ist nicht in der Lage zu tun also (hat nur lesenden Zugriff). Dadurch wird verhindert, dass die Instanz unbeabsichtigt geändert wird, wodurch Code weniger fehleranfällig wird, und es wird dokumentiert, ob der Code den Status der Instanz ändern soll oder nicht. Außerdem können Instanzen als const behandelt werden, wenn sie nicht geändert werden müssen, oder als const definiert, wenn sie nach der Initialisierung nicht geändert werden müssen, ohne dass die Funktionalität verloren geht.

Dies geschieht durch Elementfunktionen geben const CV-Qualifier , und indem Zeiger / Referenzparameter const , außer in dem Fall , dass sie den Zugang schreiben müssen.

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.

Aufgrund der Natur der const-Korrektheit beginnt dies mit den Member-Funktionen der Klasse und arbeitet nach außen. Wenn Sie versuchen, eine Nicht- const Elementfunktion von einer const Instanz oder von einer nicht- const Instanz, die als const behandelt wird, const , gibt der Compiler einen Fehler aus, wenn die cv-qualifiers verloren gehen.

Richtige Klassengestaltung

In einer const -correct-Klasse haben alle Memberfunktionen, die den logischen Status nicht ändern, this cv-Qualifikation als const this , dass sie das Objekt nicht ändern (abgesehen von mutable Feldern, die auch in const Instanzen frei modifiziert werden können ); Wenn eine const cv-qualifizierte Funktion eine Referenz zurückgibt, sollte diese Referenz auch const . Auf diese Weise können sie sowohl für konstante als auch für nicht vom CV qualifizierte Instanzen aufgerufen werden, da ein const T* entweder an ein T* oder an ein const T* binden kann. Auf diese Weise können Funktionen ihre übergebenen Parameter als const deklarieren, wenn sie nicht geändert werden müssen, ohne dass die Funktionalität verloren geht.

Ferner wird in einer const richtigen Klasse, alle bestanden-by-reference Funktionsparameter sein const richtig, wie in diskutiert Const Correct Function Parameters , so dass sie nur dann geändert werden, wenn die Funktion explizit ändern muss.

Sehen wir uns zunächst this Lebenslauf-Qualifikationsmerkmale an:

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

Wir können dies dann mit Const Correct Function Parameters kombinieren, wodurch die Klasse vollständig const -correct ist.

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

Dies kann auch mit Überladen basierend auf const kombiniert werden, in dem Fall, dass ein Verhalten gewünscht wird, wenn die Instanz const , und ein anderes Verhalten, wenn dies nicht der Fall ist. Eine häufige Verwendung hierfür sind Konstanten, die Zugriffsmethoden bereitstellen, die nur dann eine Änderung zulassen, wenn der Container selbst nicht 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]; }

    // ...
};

Dies wird allgemein in der Standard - Bibliothek verwendet, wobei die meisten Container Überlastungen bieten nehmen const ness zu berücksichtigen.

Const Correct Funktionsparameter

In einer const -correct-Funktion werden alle Parameter, die als Referenz übergeben werden, als const markiert, sofern sie nicht direkt oder indirekt von der Funktion geändert werden. Dadurch wird verhindert, dass der Programmierer versehentlich etwas ändert, das nicht geändert werden soll. Dadurch kann die Funktion sowohl nehmen const und nicht-cv-qualifizierte Instanzen, und wiederum bewirkt, dass die Instanz ist this der Typ zu sein , const T* wenn eine Elementfunktion aufgerufen wird , wo T die Klasse Typ ist.

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

Die Auswirkungen davon sind zwar weniger sichtbar als die von const korrekten Klassendesigns (bei const -correct-Funktionen und const -incorrect-Klassen werden Kompilierungsfehler verursacht, während const -correct-Klassen und const -incorrect-Funktionen ordnungsgemäß kompiliert werden), const Funktionen werden viele Fehler abfangen, durch die falsche Funktionen const würden, wie z. B. die unten stehende. [Beachten Sie jedoch, dass ein const -fehlerhafter Funktion Kompilierungsfehlern verursachen wird , wenn ein übergebenes const Beispiel , wenn es eine nicht erwartete const ein.]

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

Eine der nützlicheren Dinge bei der const Korrektheit ist die Möglichkeit, Code zu dokumentieren und dem Programmierer und anderen Benutzern bestimmte Garantien zu geben. Diese Garantien werden vom Compiler aus const Konstanz erzwungen, wobei eine mangelnde const , dass der Code sie nicht zur Verfügung stellt.

const CV-Qualifizierte Mitgliederfunktionen:

  • Es kann angenommen werden, dass jede Member-Funktion, die const ist, die Instanz lesen soll, und:
    • Ändern Sie nicht den logischen Status der Instanz, für die sie aufgerufen werden. Daher dürfen sie keine Membervariablen der Instanz ändern, für die sie aufgerufen werden, mit Ausnahme von mutable Variablen.
    • Ruft keine anderen Funktionen auf, die Mitgliedervariablen der Instanz ändern würden, mit Ausnahme von mutable Variablen.
  • Umgekehrt kann davon ausgegangen werden, dass jede Member-Funktion, die nicht const ist, die Instanz ändern möchte, und:
    • Kann den logischen Zustand ändern oder nicht ändern.
    • Ruft möglicherweise andere Funktionen auf, die den logischen Zustand ändern.

Dies kann verwendet werden, um Annahmen über den Status des Objekts zu treffen, nachdem eine bestimmte Elementfunktion aufgerufen wurde, auch ohne die Definition dieser Funktion zu sehen:

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

Aufgrund von const Regeln werden diese Annahmen tatsächlich vom Compiler durchgesetzt.

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

  • Es kann angenommen werden, dass jede Funktion mit einem oder mehreren Parametern, die const sind, die Absicht hat, diese Parameter zu lesen, und:
    • Ändern Sie diese Parameter nicht und rufen Sie keine Mitgliedsfunktionen auf, die sie ändern würden.
    • Übergeben Sie diese Parameter nicht an andere Funktionen, die sie ändern und / oder Member-Funktionen aufrufen, die sie ändern.
  • Umgekehrt kann davon ausgegangen werden, dass jede Funktion mit einem oder mehreren Parametern, die nicht const sind, beabsichtigt ist, diese Parameter zu ändern.
    • Kann diese Parameter ändern oder nicht ändern oder Member-Funktionen aufrufen, die sie ändern würden.
    • Diese Parameter können an andere Funktionen übergeben werden oder nicht, die sie modifizieren und / oder Member-Funktionen aufrufen, die sie modifizieren.

Dies kann verwendet werden, um Annahmen über den Zustand der Parameter zu treffen, nachdem sie an eine bestimmte Funktion übergeben wurden, auch ohne dass deren Definition definiert wird.

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

Aufgrund von const Regeln werden diese Annahmen tatsächlich vom Compiler durchgesetzt.

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

Während es möglich ist , zu umgehen const Korrektheit , und durch die Erweiterung diese Garantien zu brechen, muss dies absichtlich (nur Verkapselung mit Gleichem zu brechen durch den Programmierer durchgeführt wird Machiavelli , und wird wahrscheinlich dazu führen , nicht definiertes Verhalten, oben).

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.

Dies ist jedoch aufgrund der Programmierer erfordert sehr speziell den Compiler zu sagen , dass sie beabsichtigen , zu ignorieren const ness und inkonsistent über Compiler ist, ist es im Allgemeinen sicher anzunehmen , dass const richtigen Code zu tun , so unterlassen , sofern nicht anders angegeben.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow