作為文件的 Const 正確性

關於 const 正確性的一個更有用的方面是它可以作為記錄程式碼的一種方式,為程式設計師和其他使用者提供某些保證。這些保證由編譯器強制執行,因為 constness 缺乏 constness,表明程式碼不提供它們。

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.

但是,由於這需要程式設計師非常明確地告訴編譯器他們打算忽略 constness,並且在編譯器之間不一致,所以通常可以安全地假設 const 正確的程式碼將不會這樣做,除非另有說明。