EffectiveC++详解:条款03-尽可能使用 const

本文深入探讨C++中const关键字的多种应用,包括其在减少错误、成员函数声明及提高代码可读性和维护性方面的作用。同时,解析bitwiseconstness与logicalconstness的区别,以及如何在const和non-const成员函数间避免代码重复。

@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 成员函数内 不能修改类的成员,它有两个优点:

  1. 该函数可以操作 const 对象。
  2. 使得类的接口容易理解。知道哪个函数能改动对象内容,哪个函数不能。

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. 在 constnon-const 成员函数中避免重复

当某个成员函数执行很多操作时,我们要把这些操作同时放进 constnon-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 版本,必须让 *thisconst TextBlock& 通过 const_cast 变为 TextBlock&。这种做法是不安全的(反向操作是安全的)。

6. 总结

  • 将某些东西声明为 cosnt 可帮助编译器侦测出错误用法。const 可以被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  • 编译器强制实施 bitwise constness ,但你编写的程序应该使用 logical constness
  • constnon-const 成员函数有着相同的实现时(内部代码相同),令 nono-const 版本调用 const 版本可以避免重复,反之则错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值