作为文档的 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 正确的代码将不会这样做,除非另有说明。