Effective C++ 条款3 const
条款03 : 尽可能使用const
const
(不可被改动), 是一种非常有效且多样的语义约束, 有了这项约束, 我们可以借用编译器之手规范我们的代码, 以免带来意想不到的错误, 毕竟任何的改动都会伴随着一定的风险, 如果可以提前规避, 我们何乐而不为呢?
const
在实际表现上是多才多艺的, 他可以修饰对象, 对象指针, 甚至成员函数, 接下来逐一介绍 :
const 修饰变量
1 |
|
const 修饰指针
1 |
|
谈到指针就不可避免的就会想到 迭代器
, 毕竟迭代器
就是指针的封装嘛.
1 |
|
const 修饰成员函数
先明确
const
成员函数的意义 : 告知编译器这个函数内部的对象不应被改动.不是说明函数本身不可改动!!!
- 那么对成员函数声明
const
的意义何在?
- 使这个函数接口更容易被理解, 一个函数是否可以改变类内变量的具体数值会很大程度上影响我们对这个函数的定位判断.
- 使操作
const
对象成为可能, 首先我们要明晰const
对象是什么, 就是类定义出的const
对象(例如 const Stu stu(小明, 18);), 当我们声明一个类对象为const
时, 这个对象对象只能调用const
成员函数, 调用的任何non-const
成员函数都无法通过编译的, 因此如果你所设计的类有需求const
的情景时, 请设计const
成员函数.
- 这里书中给出了一个事实 : 两个成员函数如果只是常量性不同(const / non-const), 也可以被重载.
这其实就告诉我们如果想要适配const
版本的话, non-const版本和const版本各写一个就好了, 编译器会根据对象是否为const
来选择使用哪个函数, 样例如下:
1 |
|
- 接下来需要介绍两种对
成员函数为const
时应有行为的流派概念:
bitwise const :
这个流派认为如果一个成员函数为const
, 应当不改变对象中的任何变量, 也就是物理上没有1bit被改变.
logical const :
这个流派认为如果一个成员函数为const
, 可以改变对象中的某些变量, 但是不能对对象的主要逻辑产生影响, 也就是说对象在逻辑上没有被改变, 改变的部分只是起辅助优化作用, 例如修改日志, 对计算结果进行缓存, 记录当前容器大小等, 这些工作对主逻辑并没有任何影响, 却可以大大提高主逻辑的工作效率.
- 那么C++实际上是怎么定义
const
成员函数的行为的呢?
C++在一般情况下的定义按照bitwise const
的规则进行, 也就是说一个const
成员函数无法改变对象中任何变量.
但是这其中有一个C++本身不好决断的情况需要了解 :
还记得上面代码中定义的[]重载函数吗 ? const char& operator[](const std::size_t position) const
假如我把返回值改为char& : char& operator[](const std::size_t position) const
那么这样就会产生一个奇怪的情况 :
1 |
|
通过以上的情况我们可以发现, C++虽然确保在const
成员函数内部不会改变任何对象, 但是并不会检查返回对象所指向的内容是否是不可改变的, C++可能认为在函数外的行为是程序员的自由吧, 所以我们应当注意这一点.
- 那么问题又来了, 既然
logical const
也有其道理所在, C++是如何解决的呢?
C++引入了一个与const
相关的摆动场 : mutable(可变的).
mutable
的主要用途是在 const
成员函数中允许对特定成员变量的修改, 这样logical const
的诉求就可以满足了。
请阅读以下代码 :
1 |
|
以上代码将lenth
和lengthIsValid
赋予mutable
特性, 使其在const
成员函数中可以改变, 从而可以用非常小的代价更新text
的长度, 方便其他需要使用text长度的函数, 这两个变量均对text
存储字符串的主逻辑没有影响.
最后还有一个比较有价值的观点 : 我们知道要适配const
版本需要写两个类似的函数, 一个处理const
对象, 一个处理non-const
对象, 但是我们也应当发现这两个函数其实非常相似, 那么这就带来了一些问题:
- 代码重复, 这会带来阅读性降低, 维护成本提高的负面作用.
- 我们在以后的条款学习中会知道, 编译器一般会把成员函数替换为
inline
函数, 这在一般情况下肯定是更高效的, 但是inline
函数中的代码越多, 会带来一系列如代码膨胀之类的问题, 这点我们应当避免.
- 书中提出了这样的解决方案 : 令
non-const
版本调用const
版本.
这样子做的前提是两个版本的内容一定相等, 或者说non-const
版本不能修改对象内的变量, 毕竟如果修改了那和const
版本就一定不一样了, 我们来改写上面[]重载的两个版本.
1 |
|
经过以上的操作, 无论const
版本需要多少行代码, non-const
版本都只需要一行代码即可, 相当实用.
另外如果在non-const
版本虽然和const
版本十分相似, 但是还是想要修改一部分的数据, 也可以在调用完重载版本后不返回, 再进行一些修改操作再返回.
- 小问题 : 为什么不用
const
版本调用non-const
版本? 因为non-const
版本不会限制修改行为, 无法监督const
实现.
请记住
- 将某些东西声明为
const
可以帮助编译器检查出错误语法,const
可被施加于任何对象, 函数参数, 函数返回值, 成员函数 - C++在
const
成员函数定义上默认支持bitwise const
流派, 但是也通过关键字mutable
变相支持了logical const
流派 - 当
non-const
版本和const
版本等价实质时, 可以用non-const
版本调用const
版本
作者 : 天目中云