18 August 2023

C++ 核心指南目录

“By default, make member functions const

理由

如果成员函数不修改对象的外部可观测属性,我们就应该把成员变量标记为 const 。这种方式更精确地表达了我们的设计目的,他人更容易理解代码,编译器可以捕捉更多错误,有时候,我们可以更好的对代码进行优化。

错误例子

class Point {
    int x, y;
public:
    int getx() { return x; }    // BAD, should be const as it doesn't
                                // modify the object's state
    // ...
};

void f(const Point& pt)
{
    int x = pt.getx();          // ERROR, doesn't compile because getx
                                // was not marked const
}

注意

只有调用函数会修改传入的对象的时候,可以用非 const 的方式传递指针或引用给函数。代码阅读者在看到这种以无修饰的 T*T& 传递参数给函数的时候,就可以假定函数会修改所指向的对象。

注意

有些代码和库函数声明函数的参数是 T* 但是并不修改 T 对象。对于改进代码的人来说,这可能有些麻烦。你可以:

  • 更新到正确使用 const 修饰函数参数的库。这是最佳方案。
  • 使用强制类型转换,把 const 转换掉。最好避免这个方案
  • 提供一个封装函数。

例子:

void f(int* p);   // old code: f() does not modify `*p`
void f(const int* p) { f(const_cast<int*>(p)); } // wrapper

注意,这个封装函数方案只是一个补丁,只有当 f() 函数声明不能修改的时候,才可以使用。比如 f 函数是一个系统库函数,你无法修改。

注意

如果成员变量是 mutable 或是通过成员的数据的指针所指向的, const 成员函数是可以修改成员变量的值的。一个常见的应用场景是用于建立数据的缓存,从而避免重复的进行复杂计算。比如,这里有个 Date 类型,它可以缓存日期的字符串表达形式。这样就可以避免重复的计算日期的字符串表达。

class Date {
public:
    // ...
    const string& string_ref() const
    {
        if (string_val == "") compute_string_rep();
        return string_val;
    }
    // ...
private:
    void compute_string_rep() const;    // compute string
                                        // representation and place it
                                        // in string_val
    mutable string string_val;
    // ...
};

换句话说,就是常量属性是不具备传导性的。你可以在 const 成员函数中修改 mutable 成员变量,也可以通过非 const 的指针访问修改成员变量的值。这里,就需要由类本身确保,进行这些修改不会改变其向用户提供的根据其不变式语义。

请查看: Pimpl

强化

  • 标记那些没有标记为 const 的成员函数,但是却并没有对成员变量执行任何非 const 的操作运算。也就是说,这些成员函数应当被标记为 const