Effective C++——条款3(第1章)

条款03:尽量使用 const

Use const whenever possible
     const 允许指定一个语义约束(也就是指定一个"不该被改动"的对象),而编译器会强制实施这项约束.它允许告诉编译器和其他程序员其值应该保持不变.只要这(某值保持不变)是事实,因为说出来可以获得编译器的帮助,确保这条约束不被违反.
    可以用 const 在 class 外部修饰global或namespace作用域中的常量,或修饰文件,函数,或区块作用域(block scope)中被声明为 static 的对象.可以用它修饰 class 内部的 static 和non-static 成员变量.面对指针,可以指出指针自身,指针所指物,或者两者都是 const:
char greeting[] = "hello";
char *p = greeting;                        // non-const pointer,non-const data
const char *p = greeting;                // non-const pointer,const data
char * const p = greeting;                // const pointer, non-const data
const char * const p = greeting;        // const pointer, const data
    从右向左看,p是一个 const 指针,指向 const char 类型的数据.(第四个)
    从右向左看,p是一个指针,指向 const char 类型的数据.(第二个)

    STL迭代器以指针为基础塑模出来,所以 迭代器的作用就像 T * 指针.声明迭代器为 const 就像声明指针为 const 一样(即声明一个 T *const 指针),表示这个迭代器不得指向不同的对象,但它指向的对象的值是可以改变的. 如果希望迭代器所指的对象不可被改动(即 const T *指针),需要的是 const_iterator:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin();        // iter的作用像个T *const
*iter = 10;                                                    // ok. 改变iter所指对象
++iter;                                                        // error. iter是const
std::vector<int>::const_iterator cIter = vec.begin();        // cIter的作用像个const T *
*cIter = 10;                                                // error. *cIter是const
++cIter;                                                    // ok. 改变cIter
    const 最具威力的用法是面对函数对象时的应用.在一个函数声明式内,const 可以和函数返回值,各参数,函数本身产生关联.
     令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性.例如,考虑有理数的operator*的声明式:
class Rational { ... };
const Rational operator *(const Rational &lhs, const Rational &rhs);
    为什么返回一个 const 对象?原因是如果不这样客户就能实现这样的暴行:
Rational a, b, c;
(a * b) = c;
    许多程序员会无意间这样做,只因为单纯的打字错误:
if (a * b = c)                // 实际上是==
    如果a和b是内置类型,这样代码当然不合法.而一个"良好的用户自定义类型"的特征就是它们避免无端地与内置类型不兼容. 将operator *的返回值声明为 const ,可以预防那个"没有意义的赋值动作".

const 成员函数

     将 const 实施于成员函数的目的,是为了确认该成员函数可作用于 const 对象上.这一类函数之所以重要,基于两个理由:
    第一,它们使 const 接口比较容易被理解.这是因为,得知哪个函数可以改动对象内容,哪个函数不能,很重要.
     第二,它们使"操作const对象"成为可能.这对编写高效代码是个关键.因为如条款20所述, 改善C++程序效率的一个根本办法是以pass by reference to const 方式传递对象,而此技术可行的前提是,有 const 成员函数可用来处理取得的 const 对象.
    如果两个成员函数只是常量性(constness)不同,可以被重载.这是很重要的C++特性.考虑以下 class,用来表现一大块文字:
class TextBlock {
public:
    const char & operator[](std::size_t position) const {
        return text[position];
    }
    char & operator[](std::size_t position) {
        return text[position];
    }
private:
    std::string text;
};
    TextBlock的operator[]可以这样使用:
TextBlock tb("hello");
std::cout << tb[0];
const TextBlock ctb("world");
std::cout << ctb[0];
    真实程序中 const 对象大多用于pass by pointer-to-const 或 pass by reference-to-const 的传递结果.上述的ctb例子太造作,下面这个比较真实:
void print(const TextBlock &ctb) {
    std::cout << ctb[0];
}
    只要重载operator[]并对不同版本给予不同的返回类型,就可以令 const 和non-const TextBlock获得不同的处理:
tb[0] = 'x';            // ok
ctb[0] = 'x';            // error
    上述错误是因为operator[]的返回类型导致的,企图对一个"由const的operator[]返回"的 const char &施行赋值动作.
    也要注意,non-const operator[]的返回类型是个reference to char,不是char.如果operator[]只是返回一个char,下面这样的句子就无法通过编译:tb[0] = 'x';
    那是因为,如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法.纵然合法,C++以by value返回对象这一事实意味着改动的其实是tb.text[0]的一个副本,不是tb.text[0]自身.
     成员函数如果是 const 意味着什么?这里有两个流行概念:bitwise constnest(physical constness)和logical constness.
    bitwise const 阵营的人相信,成员函数只有在不更改对象的任何成员变量(static 除外)时才可以说是const.也就是说它不更改对象内的任何一个bit.这种论点的优点是很容易侦测为违反点:编译器只需寻找成员变量的赋值动作即可.bitwise constness正是C++对常量性的定义,因此 const 成员函数不可以更改对象内任何non-static 成员变量.
    但是,一个更改了"指针所指物"的成员函数虽然不能算是 const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为bitwise const 不会引发编译器异议.例如:
class CTextBlock {
public:
    char& operator[](std::size_t position) const {
        return pText[position];
    }
private:
    char *pText;
};
    这个 class 不适当地将其operator[]声明为 const 成员函数,而该函数却返回一个reference指向对象内部值.operator[]实现代码并不更改pText.于是编译器很开心地为operator[]产生目标码.它是bitwise const,所有编译器都这么认定.但是看看它允许发生什么事情:
const CTextBlock cctb("hello");
char *pc = &cctb[0];
*pc = 'J';
    创建一个常量对象并设以某值,而且只对它调用 const 成员函数.但是终究改变了它的值.
    这种情况导出所谓的logical constness.他们认为:一个 const 成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此.例如CTextBlock 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);    // error!
        lengthIsValid = true;                // error!
    }                                        // const成员函数内不能给成员变量赋值
    return textLength;
}
    textLength和lengthIsValid的修改对 const CTextBlock对象而言是可接受的,但是编译器不同意.
    解决的办法很简单: 利用C++的一个与 const 相关的摆动场:mutable(可变的).mutable 释放掉non-static 成员变量的bitwise constness约束:
class CTextBlock {
public:
    std::size_t length() const;
private:
    char *pText;
    mutable std::size_t textLength;        // 这些成员变量可能会被更改
    mutable bool lengthIsValid;            // 即使在const成员函数内
};
std::size_t CTextBlock::length() const {
    if (!lengthIsValid) {
        textLength = std::strlen(pText);    // ok
        lengthIsValid = true;                // ok
    }
    return textLength;
}
    注意:
     将某些东西声明为 const 可帮助编译器侦测出错误用法.const 可被施加于任何作用域内的对象,函数参数,函数返回值,成员函数本体.
    编译器强制实施bitwise constness,但编写程序时应该使用"概念上的常量性"(coceptual constness).
    当 const 和 non-const 成员函数有着实质等价的实现时,令non-const版本调用 const 版本可避免代码重复.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值