CppCoreGuidelines Con.2 请把成员函数默认设置为 const
18 August 2023
“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