Szukaj…


Składnia

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

Uwagi

const poprawność jest bardzo przydatnym narzędziem do rozwiązywania problemów, ponieważ pozwala programiście szybko ustalić, które funkcje mogą przypadkowo modyfikować kod. Zapobiega to również niezamierzonym błędom, takim jak ten pokazany w Const Correct Function Parameters , przed prawidłową kompilacją i niezauważeniem.

O wiele łatwiej jest zaprojektować klasę dla const poprawności, niż później dodać const poprawności do wcześniej istniejącej klasy. Jeśli to możliwe, zaprojektować dowolną klasę, która może być const poprawne tak, że jest const poprawne, aby uratować siebie i innych kłopotów później modyfikacji.

Zauważ, że można to również zastosować do volatile poprawności, jeśli to konieczne, z tymi samymi regułami, co dla const poprawności, ale jest to stosowane znacznie rzadziej.

Refrences:

ISO_CPP

Sprzedaj mi stałą poprawność

Samouczek C ++

Podstawy

const poprawność to praktyka projektowania kodu, aby tylko kod, który musi zmodyfikować instancję, był w stanie zmodyfikować instancję (tj. ma dostęp do zapisu) i odwrotnie, że żaden kod, który nie musi modyfikować instancji, nie jest w stanie tego zrobić więc (tzn. ma tylko dostęp do odczytu). Zapobiega to niezamierzonej modyfikacji instancji, dzięki czemu kod jest mniej podatny na błędy, i dokumentuje, czy kod ma na celu zmianę stanu instancji, czy nie. Pozwala również traktować instancje jako const gdy nie trzeba ich modyfikować, lub definiować jako const jeśli nie trzeba ich zmieniać po inicjalizacji, bez utraty jakiejkolwiek funkcjonalności.

Odbywa się to poprzez nadanie funkcjom członkowskim const kwalifikatorów CV oraz przez utworzenie const parametrów wskaźnika / odniesienia, z wyjątkiem sytuacji, gdy potrzebują dostępu do zapisu.

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.

Ze względu na naturę stałej poprawności zaczyna się ona od funkcji składowych klasy i działa na zewnątrz; jeśli spróbujesz wywołać funkcję składową inną niż const z instancji const lub z instancji non- const traktowanej jako const , kompilator wyświetli komunikat o błędzie utraty kwalifikatorów cv.

Const Poprawny projekt klasy

W klasie const -correct wszystkie funkcje składowe, które nie zmieniają stanu logicznego, mają this kwalifikowaną wartość cv jako const , co oznacza, że nie modyfikują obiektu (poza dowolnymi mutable polami, które można dowolnie modyfikować nawet w instancjach const ); jeśli funkcja const cv kwalifikuje się do odwołania, to odwołanie powinno być również const . Dzięki temu można je wywoływać zarówno w instancjach stałych, jak i niekwalifikowanych do CV, ponieważ const T* może wiązać się z T* lub const T* . To z kolei pozwala funkcjom zadeklarować parametry przekazane przez referencję jako const gdy nie trzeba ich modyfikować, bez utraty jakiejkolwiek funkcjonalności.

Ponadto w const właściwej klasy, wszyscy przeszli przez referencję parametry funkcji będzie const poprawne, jak to omówiono w Const Correct Function Parameters , tak że mogą one być modyfikowane tylko wtedy, gdy funkcja wyraźnie wymaga, aby je zmodyfikować.

Najpierw spójrzmy na this kwalifikatory cv:

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

Możemy następnie połączyć to z Const Correct Function Parameters , co powoduje, że klasa będzie w pełni 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 {}

Można to również połączyć z przeciążeniem opartym na const , w przypadku, gdy chcemy jednego zachowania, jeśli instancja jest const , i innego zachowania, jeśli nie jest; powszechnym zastosowaniem tego są constainery zapewniające akcesoria, które pozwalają na modyfikację tylko wtedy, gdy sam kontener nie jest 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]; }

    // ...
};

Jest to powszechnie stosowane w standardowej bibliotece, przy czym większość kontenerów zapewnia przeciążenia, aby uwzględnić const .

Const Prawidłowe parametry funkcji

W funkcji const poprawne wszystkie parametry przekazane przez referencję są oznaczone jako const chyba że funkcja bezpośrednio lub pośrednio je zmodyfikuje, zapobiegając przypadkowej zmianie przez programistę czegoś, czego nie chcieli zmienić. Umożliwia to funkcja wziąć zarówno const i instancje non-CV-wykwalifikowanych, a to z kolei powoduje, że instancja jest this , aby być typu const T* gdy funkcja członek nazywa, gdzie T jest klasa 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

Podczas gdy efekty tego są mniej natychmiast widoczne niż w przypadku const poprawnego projektowania klas (w tym const poprawne funkcje i const poprawne klasy spowodują błędy kompilacji, podczas gdy const poprawne klasy i const poprawne funkcje będą się poprawnie kompilować), const poprawne funkcje złapie wiele błędów, które const nieprawidłowych funkcji pozwoli przemknąć, takich jak ten poniżej. [Należy jednak zauważyć, że const funkcja -incorrect spowoduje błędy kompilacji jeśli przeszły const przypadek, gdy oczekuje niebędącą const jednego].

// 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 Poprawność jako dokumentacja

Jedną z bardziej przydatnych rzeczy dotyczących const poprawności jest to, że służy ona jako sposób dokumentowania kodu, zapewniając pewne gwarancje programistom i innym użytkownikom. Gwarancje te są wymuszane przez kompilator powodu const ness, z brakiem const ness kolei wskazując, że kod nie zapewnia im.

const Funkcje członka z kwalifikacją CV:

  • Można założyć, że dowolna funkcja członkowska, która jest const ma zamiar odczytać instancję i:
    • Nie modyfikują stanu logicznego instancji, do której są wywoływane. Dlatego nie mogą modyfikować żadnych zmiennych składowych instancji, do której są wywoływane, z wyjątkiem mutable zmiennych.
    • Nie będą wywoływać żadnych innych funkcji, które modyfikowałyby dowolne zmienne mutable instancji, z wyjątkiem mutable zmiennych.
  • I odwrotnie, można założyć, że dowolna funkcja składowa, która nie jest const ma zamiar zmodyfikować instancję i:
    • Może lub nie może modyfikować stanu logicznego.
    • Może lub nie może wywoływać inne funkcje modyfikujące stan logiczny.

Można to wykorzystać do przyjęcia założeń dotyczących stanu obiektu po wywołaniu dowolnej funkcji składowej, nawet bez zobaczenia definicji tej funkcji:

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

Ze względu na const reguły, te założenia będą w rzeczywistości egzekwowane przez kompilator.

// 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 Parametry funkcji:

  • Można założyć, że dowolna funkcja z jednym lub większą liczbą parametrów, które są const ma zamiar odczytać te parametry oraz:
    • Nie modyfikuje tych parametrów ani nie wywołuje żadnych funkcji członkowskich, które by je zmodyfikowały.
    • Nie przekazują tych parametrów do żadnej innej funkcji, która by je zmodyfikowała i / lub wywołała funkcje członka, które by je zmodyfikowały.
  • I odwrotnie, można założyć, że dowolna funkcja z jednym lub większą liczbą parametrów, które nie są const ma zamiar zmodyfikować te parametry oraz:
    • Może, ale nie musi, modyfikować te parametry lub wywoływać funkcje składowe, które mogłyby je zmodyfikować.
    • Może, ale nie musi, przekazać tych parametrów innym funkcjom, które je zmodyfikują i / lub wywołać dowolne funkcje członkowskie, które by je zmodyfikowały.

Można to wykorzystać do przyjęcia założeń dotyczących stanu parametrów po przekazaniu do dowolnej funkcji, nawet bez zapoznania się z definicją tej funkcji.

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

Ze względu na const reguły, te założenia będą w rzeczywistości egzekwowane przez kompilator.

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

Choć możliwe jest ominięcie const poprawności , a co za tym idzie złamać tych gwarancji, musi to być zrobione celowo przez programistę (podobnie jak złamanie hermetyzacji z Machiavelli , powyżej) i może spowodować niezdefiniowane zachowanie.

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.

Jednak ze względu na to, że wymaga od programisty bardzo dokładnego poinformowania kompilatora, że zamierza zignorować const i że jest niespójny w różnych kompilatorach, ogólnie można bezpiecznie założyć, że const poprawny kod powstrzyma się od tego, chyba że określono inaczej.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow