Поиск…


Синтаксис

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

замечания

const корректность - очень полезный инструмент устранения неполадок, так как он позволяет программисту быстро определить, какие функции могут непреднамеренно модифицировать код. Он также предотвращает непреднамеренные ошибки, такие как показанные в Const Correct Function Parameters , правильные и незаметные компиляции.

Гораздо проще создать класс для корректности const , чем позже добавить const корректность к существующему классу. Если возможно, спроектируйте любой класс, который может быть const правильным, чтобы он был const правильным, чтобы спасти себя, а другие - из-за того, что позже его модифицировали.

Обратите внимание, что это также может быть применено к volatile правильности, если необходимо, с теми же правилами, что и для const корректности, но это используется гораздо реже.

Рефракции:

ISO_CPP

Продайте меня на const const

Учебник C ++

Основы

const корректность - это практика разработки кода, так что только код, который должен изменить экземпляр, может модифицировать экземпляр (т. е. имеет доступ на запись), и, наоборот, что любой код, который не нуждается в изменении экземпляра, не может сделать поэтому (т.е. имеет доступ только для чтения). Это предотвращает непреднамеренное изменение экземпляра, делая код менее ошибочным и документирует, предназначен ли код для изменения состояния экземпляра или нет. Он также позволяет экземплярам обрабатываться как const когда они не нуждаются в модификации или определяются как const если их не нужно изменять после инициализации, не теряя при этом никакой функциональности.

Это делается путем предоставления функций-членов const CV-квалификаторов и путем создания указателей / ссылочных параметров const , за исключением случаев, когда им нужен доступ на запись.

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.

Из-за характера корректности const это начинается с функций-членов класса и выходит наружу; если вы попытаетесь вызвать функцию non- const члена из экземпляра const или из не- const экземпляра, который рассматривается как const , компилятор даст вам ошибку об этом, теряя cv-квалификаторы.

Const Correct Class Design

В const -correct класса, все функции - члены , которые не изменяются логическое состояния есть this CV-квалифицируются как const , указывая , что они не изменяют объект ( за исключением любых mutable полей, которые свободно могут быть изменены даже в const случаях ); если функция const cv-qual возвращает ссылку, эта ссылка также должна быть const . Это позволяет им вызываться как с константами, так и с не-cv-критериями, поскольку const T* может связываться с T* или const T* . Это, в свою очередь, позволяет функциям объявлять свои передаваемые по параметрам параметры как const когда их не нужно изменять, не теряя при этом никакой функциональности.

Кроме того, в const правильном классе все параметры функции pass-by-reference будут const , как описано в Const Correct Function Parameters , так что они могут быть изменены только тогда, когда функция явно должна их модифицировать.

Во-первых, давайте посмотрим на this 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();
}

Затем мы можем объединить это с Const Correct Function Parameters , в результате чего класс будет полностью 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 {}

Это также может быть объединено с перегрузки на основе const Несс, в том случае, если мы хотим одно поведение , если экземпляр является const , и другое поведение , если оно не; Обычное использование для этого - это константы, предоставляющие аксессуры, которые допускают модификацию, если сам контейнер не 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]; }

    // ...
};

Это обычно используется в стандартной библиотеке, причем большинство контейнеров обеспечения перегрузки принять const ность во внимание.

Const Правильные функциональные параметры

В const правильной функции все параметры, передаваемые по ссылке, помечены как const если функция прямо или косвенно не изменяет их, не позволяя программисту непреднамеренно изменять то, что они не хотели изменять. Это позволяет функции принимать как const и неконвертированные экземпляры, и, в свою очередь, приводит к тому, что экземпляр this типа имеет тип const T* при вызове функции-члена, где T является типом класса.

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

Хотя последствия этого менее сразу видно , чем const правильное проектирование класса (в этом const -correct функций и const -incorrect классов вызовут ошибку компиляции, в то время как const классы -correct и const -incorrect функция компилируют правильно), const правильно функции поймают много ошибок, которые будут некорректными функциями const , например, ниже. [Обратите внимание, однако, что функция const -incorrect приведет к ошибкам компиляции, если передана экземпляр const если ожидается const .]

// 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 как документация

Одна из наиболее полезных вещей о корректности const заключается в том, что она служит способом документирования кода, предоставляя определенные гарантии программисту и другим пользователям. Эти гарантии исполняются компилятором благодаря const Несс, с отсутствием const Несс , в свою очередь , указывающей , что код не обеспечивает их.

const CV-Qualified Member Functions:

  • Можно предположить, что любая функция-член, которая является const имеет намерение прочитать экземпляр и:
    • Не изменять логическое состояние экземпляра, на который они вызваны. Поэтому они не должны изменять какие-либо переменные-члены экземпляра, на который они вызывают, кроме mutable переменных.
    • Не следует вызывать любые другие функции, которые изменяли бы любые переменные-члены экземпляра, кроме mutable переменных.
  • И наоборот, любая функция-член, которая не является const может предполагать, что она имеет намерение изменить экземпляр и:
    • Может или не может изменять логическое состояние.
    • Может или не может вызывать другие функции, которые изменяют логическое состояние.

Это можно использовать, чтобы сделать предположения о состоянии объекта после вызова любой из функций-членов, даже не видя определения этой функции:

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

Из-за правил const эти предположения будут фактически выполняться компилятором.

// 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 Параметры функции:

  • Можно предположить, что любая функция с одним или несколькими параметрами, которые являются const имеет намерение читать эти параметры и:
    • Не изменять эти параметры или вызывать какие-либо функции-члены, которые бы их модифицировали.
    • Не передавать эти параметры в любую другую функцию, которая бы их модифицировала и / или вызывала любые функции-члены, которые могли бы их модифицировать.
  • И наоборот, любая функция с одним или несколькими параметрами, которые не являются const может иметь намерение изменить эти параметры и:
    • Может или не может изменять эти параметры или вызывать какие-либо функции-члены, которые должны их модифицировать.
    • Может или не может передать эти параметры другим функциям, которые будут изменять их и / или вызывать любые функции-члены, которые будут их изменять.

Это можно использовать, чтобы сделать предположения о состоянии параметров после передачи какой-либо данной функции, даже не видя определения этой функции.

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

Из-за правил const эти предположения будут фактически выполняться компилятором.

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

В то время как можно обойти const правильность , и расширением разрывать эти гарантии, то это должно быть сделано намеренно программистом (так же , как нарушение инкапсуляции с Machiavelli , выше), и, вероятно, вызовут неопределенное поведение.

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.

Однако, в связи с этим требует от программиста очень конкретно сказать компилятору , что они намерены игнорировать const ность, и быть согласованы между составителями, как правило , можно предположить , что const правильный код будет воздерживаться от этого , если не указано иное.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow