尽可能的使用const——条款03

    在c++开发中,经常会看到const关键字,比如copy构造函数、copy assignment函数中参数都有const关键字。

    在条款02中也提到过用const来替换#define修饰常量,所以const关键字是用来表示常量,即被修饰的变量初始化之后不可被修改。

一、const修饰变量

    const修饰带指针变量的时候经常会弄混淆,所以需要仔细分辨,一般分为三种情况:

例如:

    char greeting[] = "Hello";

    char* p = greeting;

    const char* p = greeting;    // 表示指针为p的char类型常量,p指针可以指向其它对象,而(*p)不能被修改

    char* const p = greeting;  // 表示p为char类型的const指针,p不能被修改,但是(*p)可以被修改

    const char* const p = greeting;   // 表示p为const char类型的const指针,p和(*p)都无法修改。

 

  const出现在星号(*)左边,表示被指物是常量;

  const出现在星号(*)右边,表示指针是常量;

  const出现在星号两边,表示被指物都是常量。

        令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。举个例子,考虑有理数(详见条款24)的operator*声明式:

class Rational { ... };
const Rational operator* (const Rational& lhs, const Rational& rhs);

        为什么返回一个const对象,原因是可能客户会犯如下错误:

Rational a, b, c;
...
if (a * b = c) ...   // 其实是想做一个比较动作

        如果a和b都是内置类型,这样的代码直接就不合法。而一个“良好的用户自定义类型”的特征是它们避免武断地与内置类型不兼容(见条款18),因此允许对两值乘积做赋值动作也就没什么意思了。

        至于const参数,没有什么特别新颖的观念,它们不过就像local const对象一样,你应该在必要使用它们的时候使用它们。除非你有需要改动参数或local对象,否则请将它们声明为const。

二、const成员函数

    const修饰成员函数的目的,是为了确认该成员函数可作用于const对象身上。这一类成员函数之所以重要,基于两个理由。

  • 第一,它们使class接口比较容易被理解。这是因为,可以确认哪个函数可以修改对象内容。
  • 第二,它们使“操作const对象”成为可能。这对编写高效代码是关键,因为条款20所言,改善C++程序效率的一个根本办法是以pass by reference-to-const方式传递对象,而此技术可行的前提是,我们有const成员函数可用来处理取得的const对象。

        另外注意两个成员函数如果只是常量性不同也是可以重载的,即我们会看到两个函数除了一个有const一个没const之外,其它都一样。

        成员函数如果是const意味什么?有两流行的概念:bitwise constness(又称physical constness)和logical constness。

        bitwise const阵营的人相信,成员函数只有在不更改对象之任何成员变量(static除外)是才可以说是const。这种论点好处是很容易侦测违反点:编译器只需寻找成员变量的赋值动作即可。bitwise constness正是C++对常量性的定义,因此const成员函数不可用更改对象内任何not-static成员变量。

        不幸的是许多成员函数虽然不十足具备const性质却能通过bitwise测试。

class CTextBlock {
public:
    ...
    char& operator[](std::size_t position) const // bitwise const 声明,
    { return pText[position]; }                  // 但其实不适当
private:
    char* pText;
};

        这个class不适当地将其operator[]声明为const成员函数,而该函数却返回一个reference指向对象内部值(条款28对此有深刻讨论)。假设暂时不管这个事实,请注意,operator[]实现代码并不更改pText。于是编译器认为它时bitwise const。但是看看它允许发生什么事:

const CTextBlock cctb("Hello");  // 声明一个常量对象。
char* pc = &cctb[0];             // 调用const operator[]取得一个指针,指向cctb的数据
*pc = 'J';                       // cctb现在有了“Jello”这样的内容。

        这其中不该有任何错误:你创建一个常量对象并设以某值,而且只对它调用const成员函数。但你终究还是改变了它的值。

        这种情况导出所谓的logical constness。这一派主张一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。例如你的CTexBlock class有可能高速缓存(cache)文本区块的长度以便应付询问:

class CTextBlock {
public:
    ...
    std::size_t length() const;
private:
    char* pText;
    std::size_t textLength;    // 最近一次计算文本区块长度。
    bool lengthIsValid;        // 目前的长度是否有效。
};

std::size_t CTextBlock::length() const {
    if (!lengthIsValid) {
        textLength = std::strlen(PText);  // 错误!在const成员函数内不能复制给textLength 
        lengthIsValid = true;             // 和lengthIsValid
    }
    return textLength;
}

        length的实现不是bitwise const,因为textLength 和lengthIsValid都可能被修改。这两笔数据被修改对const CTextBlock对象而言虽然可接受,但编译器不同意。

        解决办法很简单:用mutable(可变的)修饰not-static成员变量的bitwise constness约束:

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;
}

请记住

  • 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  • 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”。
  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

        在C++开发中,通常我们设计类的时候,应该充分考虑哪些数据成员不可被修改,哪些函数不应该修改类中的数据。善于使用const能让代码更清晰,同时也能避免一些错误。比如最简单的get和set方法设计就体现了这个思想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值