文章目录
@Author:CSU张扬
@Email:csuzhangyang@gmail.com or csuzhangyang@qq.com
@我的网站: https://www.cppbug.com
条款03-尽可能使用 const
1. 复习 const 的用法
可以通过这篇文章先复习一下 const 的使用: 顶层/底层const or 顶层/底层const
简单介绍一下 const 的使用:
- 顶层
const,表示指针本身是常量 - 底层
const,表示指针所指对象是个常量
char s[] = "hello";
char* p1 = s; // 非底层,非顶层
const char* p2 = s; // 底层,和 char const * p2 = s;相同,仅写法不同
char* const p3 = s; // 顶层
const char* const p4 = s; // 底层,顶层
int i = 1;
const int& j = i; // 顶层
const int k = i; // 顶层
2. 函数返回常量可以减少错误
class Rational {... ...};
const Rational operator* (const Rational& lhs, const Rational& rhs);
这里重载 * 函数,返回的是一个 const。
如果我们返回的不是 const,那么下面代码就会产生很难排查的问题,因为编译器认为它是对的。
int main() {
Rational a, b ,c;
if ((a * b) = c) {
... ...;
}
}
(a * b) = c ,你可能疑惑为什么有人会在 a * b 的结果上赋值,或许这个人只是想比较是否相等,而少写了个 = 。但是编译器认为这就是个赋值操作(赋值操作也可以转为 bool 类型),没有任何语法问题。
我们的代码就会遇到问题,因为无论怎么样 (a * b) = c 都是 真 ,我们会浪费时间来排查这种逻辑错误。
如果返回的是 const ,那么这个赋值操作就是不允许的,编译器就会报错。
3. const 成员函数
const 成员函数内 不能修改类的成员,它有两个优点:
- 该函数可以操作
const对象。 - 使得类的接口容易理解。知道哪个函数能改动对象内容,哪个函数不能。
4. 两个流行概念 bitwise constness 、logical constness
4.1 bitwise constness
bitwise constness 阵营的人认为,成员函数只有在不改变对象的任何成员变量(static 除外)时,才是 const 的,也就是说它不更改对象内的任何一个 bit 。编译器采取的标准就是这个。
然而,一些成员函数虽然不完全具备 const 的性质,却能通过 bitwise 测试。具体来说,一个更改了 指针所指物 的成员函数虽然不算是 const,但如果只有指针(而非其所指物)属于对象,那么此函数为 bitwise constness 。
class CTextBlock {
public:
...
char& operator[] (std::size_t position) const {
return pText[position];
}
private:
char* pText;
}
这段代码的 operator[] 声明为 const 的,这确实是 bitwise constness 的,但是返回的确实一个内部值,我们可以通过这个返回值来修改我们的对象。
const CTextBlock cctb("hello");
char* pc = &cctb[0];
*pc = 'j'; // 此时变为 "jello"
cctb[1] = 'k'; // 此时变为 "jkllo"
我们创建了一个常量对象,而且只调用了 const 成员函数,但是我们最终还是改变了它的值。
注: 如果我们确实需要保护一个 指针成员所指物,使其不会被改变。我们可以将该指针封装到一个模板中,具体做法参考:在c++中,如果成员指针指向某些数据,如何保护该数据不被修改?
4.2 logical constness
由此,我们引出了另一个概念 logical constness :一个 const 成员函数可以修改它所处理的对象内的某些 bits ,但只有当客户端侦测不出的情况下才可以。如下例所示,我们可以在 const 成员函数里修改一些 私有成员变量,这些变量对于用户来说是看不见的,所以他们不会知道 也不必知道这些变化。
我们使用 mutable 关键字修饰的变量,即使在 const 成员函数内也可以被修改。
class CTextBlock {
public:
... ...;
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const {
if (!lengthIsValid) {
textLength = std::strlen(pText);
lengthIsValid = true;
}
return textLength;
}
5. 在 const 和 non-const 成员函数中避免重复
当某个成员函数执行很多操作时,我们要把这些操作同时放进 const 和 non-const 成员函数中,这样的代码就很长且重复。
class TextBlock {
public:
...
char& operator[] (std::size_t position) {
... //
... // 其他操作
return text[position];
}
char& operator[] (std::size_t position) const {
... //
... // 其他操作
return text[position];
private:
std::string text;
}
因此我们可以令其中一个去调用另一个。
class TextBlock {
public:
...
char& operator[] (std::size_t position) {
... //
... // 其他操作
return text[position];
}
char& operator[] (std::size_t position) const {
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
private:
char* pText;
}
我们先将 *this 由原来的 TextBlock& 转型为 const TextBlock& ,使它可以调用 cosnt 版本的函数,cosnt 版本的函数返回的是一个 const char&,因此我们还要对返回值转型,使用 const_cat<char&> 移除返回值的 const。
注意:反向做法——令 const 版本调用 non-const 版本是错误的。
因为 non-const 函数并不承诺不改变成员变量,我们可能会改变 不应改动的对象。
同时,我们要想const 版本调用 non-const 版本,必须让 *this 由 const TextBlock& 通过 const_cast 变为 TextBlock&。这种做法是不安全的(反向操作是安全的)。
6. 总结
- 将某些东西声明为
cosnt可帮助编译器侦测出错误用法。const可以被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。 - 编译器强制实施 bitwise constness ,但你编写的程序应该使用 logical constness 。
- 当
const和non-const成员函数有着相同的实现时(内部代码相同),令nono-const版本调用const版本可以避免重复,反之则错误。
本文深入探讨C++中const关键字的多种应用,包括其在减少错误、成员函数声明及提高代码可读性和维护性方面的作用。同时,解析bitwiseconstness与logicalconstness的区别,以及如何在const和non-const成员函数间避免代码重复。
1007

被折叠的 条评论
为什么被折叠?



