Effective C++ 之 Item 3:尽可能使用 const
Effective C++
Chapter 1. 让自己习惯C++(Accustoming Yourself to C++)
Item 3. 尽可能使用 const (Use const whenever possible)
1. const 与语义约束
const 允许指定一个语义约束(也就是指定一个“不该被改动”的对象),而编译器强制实施这项约束。它可以在 classes 外部修饰 global 或 namespace(见 Item2)作用域中的常量,或修饰文件、函数、或区块作用域(block scope)中被声明为 static 的对象。还可以修饰 classes 内部的 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
const 语法变化多端,但并不高深莫测。 如果关键字 const 出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针本身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
注意:如果被指物是常量,关键字 const 写在类型之前,与关键字写在类型之后/星号之前,两者写法的意义相同.即下列两个函数接受的参数类型是一样的:
- void f1(const Widget* pw); // f1 获得一个指针,指向一个常量的(不变的)Widget 对象
- void f2(Widget const * pw); // f2 也是
2. const 与 STL 迭代器
STL 迭代器是以指针为根据塑模出来的,所以迭代器的作用就像个 T* 指针。声明迭代器为 const 就像声明指针为 const 一样(即声明一个 T* const 指针),表示迭代器不得指向不同的东西,但它所指东西的值是可以改动的。如果希望迭代器所指的东西不可被改动(即希望 STL 模拟一个 const T* 指针),你需要的是 const_iterator:
- std::vector<int> vec;
- ...
- const std::vector<int>::iterator iter = vec.begin(); // iter 的作用像个 T* const
- *iter = 10; // 没问题,改变 iter 所指物
- ++iter; // 错误! iter 是 const
- std::vector<int>::const_iterator cIter = vec.begin(); // cIter 的作用像个 const T*
- *cIter = 10; // 错误! *cIter 是 const
- ++cIter; // 没问题,改变 cIter
3. const 与函数声明
const 最具威力的用法是面对函数声明时的应用。在一个函数声明式内, const 可以和函数返回值、各参数、函数自身(如果是成员函数)产生关联。令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。如有理数的 operator* 声明式:
- class Rational { ... };
- const Rational operator* (const Rational& lhs, const Rational& rhs);
返回一个 const 对象可以避免出现这样的行为:
- Rational a, b, c;
- ...
- (a * b) = c; //在 a * b 的成果上调用 operator=
这种情况可能由打字错误(以及一个可被隐式转换为 bool 的类型)发生,却不易被发现:
- if (a * b = c) ... //其实是想做一个比较动作
至于 const 参数,它们不过像 local const 对象一样,应该在必要使用它们的时候使用它们。除非有需要改动参数或 local 对象,否则请将它们声明为 const 。
4. const 与成员函数
将 const 实施于成员函数的目的,是为了确认该成员函数可作用于 const 对象身上。这一类成员函数之所以重要,基于两个理由:
- 它们使 class 借口比较容易被理解。因为得知哪个函数可以改动对象内容而哪个函数不行很重要。
- 它们使“操作 const 对象”成为可能。这对编写高效代码很关键,因为改善 C++ 程序效率的一个根本办法是以 pass by reference-to-const 方式传递对象,而此技术可行的前提是有 const 成员函数可用来处理取得(并经修饰而成)的 const 对象。
两个成员函数如果只是常量性(constness)不同,可以被重载,这是一个重要的 C++ 特性。
- class TextBlock
- {
- public:
- ...
- const char& operator[] (std::size_t position) const // operator[] for const 对象
- { return text[position]; }
- char& operator[] (std::size_t position) // operator[] for non-const 对象
- { return text[position]; }
- private:
- std::string text;
- };
TextBlock 的 operator[]s 可以这么使用:
- TextBlock tb("Hello");
- std::cout << tb[0]; // 调用 non-const TextBlock::operator[]
- const TextBlock ctb("World");
- std::cout << ctb[0]; // 调用 const TextBlock::operator[]
真实程序中 const 对象大多用于 passed by pointer-to-const 或 passed by reference-to-const 的传递结果。上述的 ctb 太过造作,下面这个比较真实:
- void print(const TextBlock& ctb) //此函数中 ctb 是 const
- {
- std::cout << ctb[0]; //调用 const TextBlock::operator[]
- ...
- }
只要重载 operator[] 并对不同的版本给予不同的返回类型,就可以令 const 和 non-const TextBlock 获得不同的处理:
- std::cout << tb[0]; //没问题,读一个 non-const TextBlock
- tb[0] = 'x'; //没问题,写一个 non-const TextBlock
- std::cout << ctb[0]; //没问题,读一个 const TextBlock
- ctb[0] = 'x'; //错误!! 写一个 const TextBlock, operator[]调用动作没问题,错在为函数返回的 const char& 进行赋值
注意 non-const operator[] 的返回类型是个 reference to char, 不是 char。如果 operator[] 只是返回一个 char,下面的句子就无法通过编译:
- tb[0] = 'x';
因为如果函数的返回类型是个内置类型,改动函数返回值从来就不合法。纵使合法, C++ 以 by value 返回对象这一事实意味着被改动的其实是 tb.text[0] 的一个副本,不是其自身,这不会是你想要的。
成员函数如果是 const 意味着什么?有两个流行概念:bitwise constness(又称 physical constness)和(logical constness)。
bitwise const 阵营的人认为成员函数只有在不更改对象之任何成员变量(static 除外)时才可以说是 const 。也就是说它不更改对象内的任何一个bit。这种论点的好处是很容易侦测违反点:编译器只需寻找成员变量的赋值动作即可。bitwise constness 正是 C++ 对常量性(constness)的定义,因此 const 成员函数不可以更改对象内任何 non-static 成员变量。
但许多成员函数虽然不十足具备 const 性质却能通过 bitwise 测试。更具体地说,一个更改了”指针所指物“的成员函数虽然不能算是 const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为 bitwise const 不会引发编译器异议。这导致反直观结果。
- class CTextBlock
- {
- public:
- ...
- char& operator[](std::size_t position) const // bitwise const 声明,但其实不适当
- { return pText[position]; }
- private:
- char* pText;
- };
这个 class 不适当地将其 operator[] 声明为 const 成员函数,而该函数却返回一个 reference 指向对象内部值(详见 Item 28) 。假设暂时不管这个事实, operator[] 实现代码并不更改 pText, 它是 bitwise const, 编译器为 operator[] 产出目标码,但是:
- const CTextBlock cctb("hello"); //声明一个常量对象
- char* pc = &cctb[0]; //调用 const operator[] 取得一个指针,指向 cctb 的数据
- *pc = 'J'; //cctb 现在有了 "Jello" 这样的内容
此处你创建一个常量对象并设以某值,而且只对它调用 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); //错误!在 const 成员函数内不能赋值给 textLength 和 lengthIdValid。
- lengthIsVaild = true;
- }
- return textLength;
- }
length 的实现不是 bitwise const,因为 textLength 和 lengthIsValid 都可能被修改。这两笔数据被修改对 const CTextBlock 对象而言虽然可接受,但编译器不同意,它们坚持 bitwise constness。解决之道是利用 C++ 的一个与 const 相关的摆动场:mutable(可变的)。mutable 释放掉 non-static 成员变量的 bitwise constness 约束:
- class CTextBlock
- {
- public:
- ...
- std::size_t length() const;
- private:
- char* pText;
- mutable std::size_t textLength; //这些成员变量可能总是会被修改,即使在 const 成员函数内。
- mutable bool lengthIsValid;
- };
- std::size_t CTextBlock::length() const
- {
- if (!lengthIsValid)
- {
- textLength = std::strlen(pText); //现在可以这样。
- lengthIsVaild = true; //也可以这样。
- }
- return textLength;
- }
在 const 和 non-const 成员函数中重复了一些代码,所以最佳的方式是实现 operator[] 的机能一次并使用它两次。也就是说,必须令其中一个调用另一个。这促使我们将常量性转除(casting sway constness) 。
例子中 const operator[] 完全做掉了 non-const 版本该做的一切,唯一的不同是其返回类型多了一个 const 资格修饰。这种情况下如果将返回值的 const 转除是安全的,因为不论谁调用 non-const operator[] 都一定先有个 non-const 对象,否则就不能够调用 non-const 函数。所以令 non-const operator[] 调用其 const 兄弟是一个避免代码重复的安全做法 —— 即使过程中需要一个转型动作。
- class TextBlock
- {
- public:
- ...
- const char& operator[](std::size_t position) const
- {
- ... //边界检验(bounds checking)
- ... //志记数据访问(log access data)
- ... //检验数据完整性(verify data integrity)
- return text[position];
- }
- char& operator[](std::size_t position) //现在只调用 const op[]
- {
- return const_cast<char&>( static_cast<const TextBlock&>(*this)[position] ); //将 op[] 返回值的 const 转除为 *this 加上 const
- //调用 const op[]
- }
- ...
- };
这份代码有两个转型动作,而不是一个,我们打算让 non-const operator[] 调用其 const 兄弟,但 non-const operator[] 内部若只是单纯调用 operator[] ,会递归调用自己。为了避免无穷递归,我们必须明确指出调用的是 const operator[] ,但 C++ 缺乏直接的语法可以这么做,因此这里将 *this 从其原型 TextBlock& 转型为 const TextBlock& 。所以这里有两次转型:
- 第一次用来为 *this 添加 const(这使接下来调用 operator[] 时得以调用 const 版本);
- 第二次则是从 const operator[] 的返回值中移除 const 。
添加 const 的那一次转型强迫进行了一次安全转型(将 non-const 对象转为 const 对象),所以我们使用 static_cast 。移除 const 的那个动作只可以借由 const_cast 完成,没有其他选择。
更值得了解的是反向做法 —— 令 const 版本调用 non-const 版本以避免重复 —— 并不是你该做的事。记住, const 成员函数承诺绝不改变其对象的逻辑状态(logical state), non-const 成员函数却没有这般承诺。如果在 const 函数内调用 non-const 函数,就是冒了这样的风险:你曾经承诺不改动的那个对象被改动了。
const 是个奇妙且非比寻常的东西。在指针和迭代器身上;在指针、迭代器及 reference 指涉的对象身上;在函数参数和返回类型身上;在 local 变量身上;在成员函数身上;······。尽可能使用它。
请记住:
- 将某些东西声明为 const 可帮助编译器侦测出错误用法。 const 可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
- 编译器强制实施 bitwise constness,但你编写程序时应该使用”概念上的常量性“(conceptual constness)。
- 当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可以避免代码重复。
Effective C++ 之 Item 3:尽可能使用 const的更多相关文章
- Effective C++ 条款03:尽可能使用const
场景一 用于修饰指针 char greeting[] = "Hello"; char* p = greeting; // non-const pointer, non-const ...
- Effective C++ 之 Item 2:尽量以 const, enum, inline 替换 #define
Effective C++ Chapter 1. 让自己习惯C++(Accustoming Yourself to C++) Item 2. 尽量以 const, enum, inline 替换 #d ...
- Effective STL 笔记 -- Item 6 ~ 7: Container and Object Pointer
Effective STL 笔记 – Item 6 ~ 7: Container and Object Pointer 中间两次笔记被删掉了,简单补一下: Item 3 中提到如果将对象直接放入容器中 ...
- Effective C++ -----条款03:尽可能使用const
如果关键字const出现在星号左边,表示被指物是常量:如果出现在星号右边,表示指针自身是常量:如果出现在星号两边,表示被指物和指针两者都是常量. char greeting[] = " he ...
- Effective C++_笔记_条款03_尽可能使用const
(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 关键字const多才多艺,语法变化多端.关于const的基本用法 ...
- 读书笔记 effective c++ Item 2 尽量使用const,枚举(enums),内联(inlines),不要使用宏定义(define)
这个条目叫做,尽量使用编译器而不要使用预处理器更好.#define并没有当作语言本身的一部分. 例如下面的例子: #define ASPECT_RATIO 1.653 符号名称永远不会被编译器看到.它 ...
- 《Effective C++》读书笔记 条款03 尽可能使用const 使代码更加健壮
如果你对const足够了解,只需记住以下结论即可: 将某些东西声明为const可帮助编译器侦测出错误用法,const可被施加于任何作用于内的对象.函数参数.函数返回类型.成员函数本体. 编译器强制实施 ...
- Effective C++ 条款三 尽可能使用const
参考资料:http://blog.csdn.net/bizhu12/article/details/6672723 const的常用用法小结 1.用于定义常量变量,这样这个变量在后面就不可以 ...
- Effective C++ 之 Item 6 : 若不想使用编译器自动生成的函数,就该明确拒绝
Effective C++ chapter 2. 构造 / 析构 / 赋值运算 (Constructors, Destructors, and Assignment Operators) Item 6 ...
随机推荐
- 【leetcode】Word Ladder
Word Ladder Total Accepted: 24823 Total Submissions: 135014My Submissions Given two words (start and ...
- c#图片输出
1: Response.BinaryWrite() 其实就是和输出文字一样 只是图片是流的形式; delegate long myDel(int first, int second); FileSt ...
- 项目管理工具~SVN
SVN 定期更新:每周五,周一早上 目录完备: 需求文档 设计文档 数据字典 测试报告 代码备份 周报月报 ...
- C#.NET如何不序列化字段、属性
当我们使用公开属性以及公开字段时,都可以顺利的被序列化, 01.[Serializable] 02.public class MyClass 03.{ 04. public int ID; 05 ...
- ODBC连接发生错误:未发现数据源名称并且未指定默认驱动程序
程序在使用ODBC方式连接数据库时发生错误: ERROR [IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序. 什么原因造成的呢? 本人使用&l ...
- [Android Pro] root用户删除文件提示:Operation not permitted
reference to : http://blog.csdn.net/evanbai/article/details/6187578 一些文件看上去可能一切正常,但当您尝试删除的时候,居然也会报错, ...
- 解决spring+shiro cacheManager 登录报错
一.项目启动,登录报错 org.springframework.beans.factory.BeanCreationException: Error creating bean with name ' ...
- sendto : Permission denied
遇到如题的问题,google了一番,找到了解决方法,写下来备用 问题: udp发送数据时候报错sendto error : Permission denied 改正方法: 在创建了套接字后,加上下列 ...
- hdu2030 汉字统计
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2030 解题思路:主要考察汉字的编码方式, 汉字机内码在计算机的表达方式的描述是,使用二个字节,汉字的每 ...
- 两个viewport的故事(第一部分)
原文:http://www.quirksmode.org/mobile/viewports.html 在这个迷你系列的文章里边我将会解释viewport,以及许多重要元素的宽度是如何工作的,比如< ...