读书笔记 effective c++ Item3 在任何可能的时候使用 const
Const可以修饰什么?
Const 关键字是万能的,在类外部,你可以用它修饰全局的或者命名空间范围内的常量,也可以用它来修饰文件,函数和块作用域的静态常量。在类内部,你可以使用它来声明静态或者非静态的数据成员。对于指针来说,你可以指定指针本身是不是const,指针指向的数据是不是const,两者可以同时为const或者两者同时为非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,如果关键字const出现在星号的右侧,那么指针本身是const;如果星号出现在两侧,那么两者都为const.
当指针指向的内容为const时,一些程序员将const放在类型之前,一些程序员将const放在类型之后,星号之前,这两者在意义上是等同的,下面的两个函数有相同的参数类型:
void f1(const Widget *pw);
void f2(Widget const *pw);
因为两种形式在代码中都是存在的,因此应该能够识别两者。
Const在迭代器中应用
STL迭代器是模仿的指针,因此迭代器的行为非常像一个指针,声明一个迭代器的const就像声明一个指针的const: 迭代器不允许指向不同的东西,但迭代器指向的东西是可以改变的。如果你想让迭代器指向的东西不能被修改,这时你就需要一个const_iterator.
std::vector<int> vec;
…
const std::vector<int>::iterator iter= vec.begin();//iter acts like a T*const
*iter = ;//OK
++iter;//error
std::vector<int>::const_iterator citer = vec.begin();//citer acts like a T const *
*citer = ;//error
++citer;//OK
Const修饰函数返回值和函数参数
Const 的一些强大的用法来源于在函数声明上的应用。在一个函数声明中,const可以修饰函数返回值,函数参数,对于成员函数来说,可以修饰整个函数。
一个函数返回一个const值在一般情况下是不合适的。但有时在保证安全和效率的情况下可以减少客户端的出错率。举个例子,为有理数声明一个operator*函数。
class Rational{…};
const Rational operator*(const Rational&lhs,const Rational&rhs);
为什么operator*的结果是一个const对象?因为如果不是const,客户端可以提交以下的暴行:
Rational a,b,c;
…
(a*b)=c;//invoke operator= on the result of a*b
我不知道为什么程序员会对两个数的乘积进行赋值,但我确实知道程序员虽然不想这么做,但确实这么做了。所有都是因为一个简单的输入错误(一个可以隐式转换成bool的类型):
If(a*b=c)…
如果a和b是内建类型,那么上面的代码是不合法的,一个好的用户自定义类型的特点是它们避免同内建类型的无端的不兼容(上面的代码如果内建类型不合法,那么用户自定义类型也应该不合法)。因此对两个数的乘积进行赋值也就没有理由这么做了。将operator*的返回值声明成const可以阻止这种赋值操作。
对于const参数来说没有什么特别新的,它们就像local的const对象,你应该在任何可以使用它们的时候使用它。除非你像更改一个参数或者本地对象,否则确保它被声明成const,敲打六个字符的努力会可以使你免于类似上面的错误的打搅。
Const 成员函数介绍
将const作用于成员函数上面的意图是能够确认哪些成员函数可以通过const对象被调用。这类成员函数很重要,原因有两条:一,它们使得类的接口更容易被理解,知道哪些函数可以改变对象哪些不可以,这一点很重要。二,它们使得和const对象一起工作成为可能,这是编写高效代码的很重要的方面,因为Item20解释道,提高c++程序性能的基本方法是按const引用传递对象。这项是可行的前提是,const成员函数能够处理const修饰的对象。
许多人忽略了一个事实,不同常量性的两个成员函数是可以重载的,这时c++的重要特性。考虑一个表现文本块的类:
class TextBlock { public: … const char&operator[](std::size_t positioin) 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[];//call non-const operator[] Const TextBlock ctb(“World”); Std::cout<<ctb[];//call const TextBlock::operator[]
顺便说一下,将指向const对象的指针或者指向const对象的引用作为参数经常出现在真实世界的程序中。上面的关于ctb的例子过于造作。下面的例子更符合实际:
void Print(const TextBlock &ctb) {
std::cout<<ctb[];//call const TextBlock::operator[]
}
通过重载operator[],给不同的函数版本不同的返回值类型,可以对 const和非const TextBlocks进行不同处理:
std::cout << tb[]; // fine — reading a non-const TextBlock tb[] = ’x’; // fine — writing a non-const TextBlock std::cout << ctb[]; // fine — reading a const TextBlock ctb[] = ’x’; // error! — writing a const TextBlock
注意这里错误的出现是由于调用operator[]的返回值引起的,调用operator[]本身没有问题。问题出现在尝试给一个const char&类型的变量进行赋值操作, const char&是const 版本的operator[]的返回值类型。
同时需要注意non-const operator[]的返回值类型是指向char的引用,如果operator[]返回一个char,那么下面的句子将不能通过编译:
tb[] = ‘x’;
因为尝试修改内建类型的函数返回值是不合法的。即使是合法的,c++中的按值返回(Item20)意味着我们修改的是tb.text[0]的拷贝,而不是tb.text[0]本身,这不是你需要的行为。
Const成员函数的两个派别
将成员函数声明成const意味这什么,对于这个问题有两种流行的见解:bitwise
constness (also known as physical constness) and logical constness。
Bitwise const阵营相信当且仅当成员函数不修改对象的任何数据成员(不包括statics数据成员)时它才是const成员函数,举个例子:成员函数没有修改对象内部的任意bits.bitwise
const的一个好处是能够比较容易的识别出反例:编译器只要寻找对数据成员的赋值就可以了。事实上,bitwise
const是常量性的c++定义,const成员函数不允许修改对象的任何非静态数据成员。
不幸的是,许多成员函数没有表现的特别常量性,而不能通过bitwise-const测试。特别的,一个成员函数修改指针指向的内容没有表现出常量性。但是如果指针是在对象内部,这个函数是bitwise
const的,编译器不会发出抱怨。这会导致一个违反直觉的行为。举个例子,我们有个像testBlock的类,用char*而不是string来存储数据,因为它需要同一个不是识别string的C-API进行通讯
class CTextBlock { public: ... char& operator[](std::size_t position) const // inappropriate (but bitwise { return pText[position]; } // const) declaration of operator[] private: char *pText; };
这个类(不恰当的)将operator[]声明为一个const成员函数,而函数返回的是指向对象内部数据的引用(更深层次的讨论见Item28)。把它抛到一边,注意到operator[]的实现没有以任何方式修改pText。因此,编译器很高兴的为operator[]生成了代码,它毕竟是bitwise const的,这时编译器检查的所有东西,但是看一下允许发生什么:
const CTextBlock cctb("Hello"); // declare constant object char *pc = &cctb[]; // call the const operator[] to get a pointer to cctb’s data *pc = ’J’; // cctb now has the value “Jello”
当你用特定值创建一个const对象的时候,这里出现了错误,你只是调用了对象的const成员函数,但是值仍然被修改了!
因此产生了logical constness的概念,这种观点的拥护者辩论到,一个const成员函数可以修改对象内部的一些bits,但是只有在客户端不能够内侦测到的情况下才行。
logical constness的两个问题
- 问题一
class CTextBlock { public: ... std::size_t length() const; private: char *pText; std::size_t textLength; // last calculated length of textblock bool lengthIsValid; // whether length is currently valid }; std::size_t CTextBlock::length() const { if (!lengthIsValid) { textLength = std::strlen(pText); // error! can’t assign to textLength lengthIsValid = true; // and lengthIsValid in a const } // member function return textLength; }
Length()的这种实现当然不是bitwise const的,textLength和lengthIsValid都有可能被修改。但是看上去这个函数对const CTextBlock对象来说应该是有效的。编译器不同意。它们坚持bitwise constness。该怎么做?
解决方法比较简单:利用c++常量相关的扭动空间,也就是mutable,mutable将数据成员从bitwise constness的约束中释放出来:
class CTextBlock { public: ... std::size_t length() const; private: char *pText; mutable std::size_t textLength; // these data members may mutable bool lengthIsValid; // always be modified, even in const member functions std::size_t CTextBlock::length() const { if (!lengthIsValid) { textLength = std::strlen(pText); // now fine lengthIsValid = true; // also fine } return textLength; }
- 问题二
Mutable对于对bitwise-constness不是很介意的问题是一个好的解决方法,但是它不能够解决所有的const相关的难题。例如:假设TextBlock中的operator[]不仅返回一个指向合适字符的引用,它同时执行边界检查,为访问信息打印日志,可能甚至会检查数据完整性。将所有这些同时放在const和non-const operator[](Item30)函数中,产生了下面这种怪胎:
class TextBlock { public: ... const char& operator[](std::size_t position) const { ... // do bounds checking ... // log access data ... // verify data integrity return text[position]; } char& operator[](std::size_t position) { ... // do bounds checking ... // log access data ... // verify data integrity return text[position]; } private: std::string text; };
你能说出代码重复,编译时间变长,不易维护,代码膨胀等令人头痛的问题么?当然,可以把边界检查等所有代码移到一个单独的成员函数中(当然是private的),两个版本的opeator[]都会调用这个函数,但是仍然会有重复调用函数,仍然会有重复的返回语句。
你真正需要的是之实现operator[]一次,而使用两次。也就是你需要用一个版本的operator[]去调用另一个版本的operator[]。这把我们带到了如何将constness去掉(casting)的问题。
作为一个通用的规则,casting是一个坏的方法,我用了一整个条款(Item27)来告诉你不要使用casting,但是代码重复也是不令人愉快的。operator[]的const版本做了non-const版本要做的所有事情,唯一的区别是有一个const返回类型。去掉返回值的常量性是安全的,任何人调用non-const operator[]必须在第一个位置有一个non-const的对象。否则它们不能够被叫做non-const函数。所以non-const operator[]调用const版本是防止代码重复的安全的方法,即使需要cast.下面是代码,你在读完后面的解释后可能会更加清楚。
class TextBlock { public: ... const char& operator[](std::size_t position) const // same as before { ... ... ... return text[position]; } char& operator[](std::size_t position) // now just calls const op[] { return const_cast<char&>( // cast away const on op[]’s return type; static_cast<const TextBlock&>(*this) [position]//add const to *this’s type call const version of op[] ); } ... };
你所看到的是,代码使用了两个cast,我们想让operator[]的non-const版本调用const版本,但是如果在non-operator[]内部,我们只是调用operator[],我们会递归的调用自己。为了防止无限的递归调用,必须指定我们调用的是const operator[],但是没有直接的方法做到这一点。因此我们需要将*this从原生类型TextBlock& 转换(cast)成const TextBlock&。是的,我们使用cast来添加const.因此我们会使用两个cast:一个为*this添加const,另一个将const从operator[]的返回值中移除。
添加const的cast只是强制进行安全转型(从non-const转换成const),所以我们使用static_cast.将常量性移除只有通过const_cast才能完成,我们没有别的选择。(从技术上来说,一个C风格的cast也能达到目的,但是,正如Item27所描述的,这样的cast不是正确的选择。如果你不熟悉static_cast或者const_cast,参考Item27).
在这个例子中,我们调用的是一个operator,所以语法看起来有些奇怪。这个结果不能赢得选美大赛,但是依赖const版本的函数来实现non-const版本的函数,达到了避免代码重复的效果。为了达到目标而写出如此难看的语法,是你能够决定的,但是依靠const函数来实现non-const成员函数的技术是绝对值得了解的。
更值得了解的是,反向调用-也就是通过const来调用non-const版本的函数来避免代码重复-不是你应该做的。需要记住,const成员函数承诺绝不修改对象的逻辑状态,但是non-const函数并没有这样的承诺。如果你从一个const成员函数中调用一个non-const成员函数,就会出现你承诺不应该被修改的东西最终被修改的风险,这也是为什么用const成员函数调用non-const成员函数是错误的:对象有可能被改变。事实上,为了是代码通过编译,你需要使用const_cast将*this的常量性去除,这是会出现麻烦的迹象。相反的调用顺序是安全的,一个non-const成员函数可以对一个对象做任何它想做的,所以调用const成员函数是没有危险。这也是为什么static_cast可以应用在*this上的原因:没有const相关的危险。
读书笔记 effective c++ Item3 在任何可能的时候使用 const的更多相关文章
- 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库
1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...
- 读书笔记——Effective C++
1.让自己习惯C++ 条款01:视C++为一个语言联邦 C++高效编程守则视状况而变化,取决于你使用C++的哪一部分. 条款02:尽量以const.enum.inline替换 #define 对于单纯 ...
- 读书笔记 effective c++ Item4 确保对象被使用前进行初始化
Item4 确保对象被使用前进行初始化 C++在对象的初始化上是变化无常的,例如看下面的例子: Int x; 在一些上下文中,x保证会被初始化成0,在其他一些情况下却不能够保证.看下面的例子: Cla ...
- 读书笔记 effective c++ Item 5 了解c++默认生成并调用的函数
1 编译器会默认生成哪些函数 什么时候空类不再是一个空类?答案是用c++处理的空类.如果你自己不声明,编译器会为你声明它们自己版本的拷贝构造函数,拷贝赋值运算符和析构函数,如果你一个构造函数都没有声 ...
- 读书笔记 effective c++ Item 2 尽量使用const,枚举(enums),内联(inlines),不要使用宏定义(define)
这个条目叫做,尽量使用编译器而不要使用预处理器更好.#define并没有当作语言本身的一部分. 例如下面的例子: #define ASPECT_RATIO 1.653 符号名称永远不会被编译器看到.它 ...
- 读书笔记 effective c++ Item 1 将c++视为一个语言联邦
Item 1 将c++视为一个语言联邦 如今的c++已经是一个多重泛型变成语言.支持过程化,面向对象,函数式,泛型和元编程的组合.这种强大使得c++无可匹敌,却也带来了一些问题.所有“合适的”规则看上 ...
- 读书笔记 effective c++ Item 6 如果你不想使用编译器自动生成的函数,你需要明确拒绝
问题描述-阻止对象的拷贝 现实生活中的房产中介卖房子,一个服务于这个中介的软件系统很自然的会有一个表示要被销售的房屋的类: class HomeForSale { ... }; 每个房产中介会立刻指出 ...
- 读书笔记 effective c++ Item 8 不要让异常(exceptions)离开析构函数
1.为什么c++不喜欢析构函数抛出异常 C++并没有禁止析构函数出现异常,但是它肯定不鼓励这么做.这是有原因的,考虑下面的代码: class Widget { public: ... ~Widget( ...
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
随机推荐
- linux 驱动入门6
看/sys目录经常看到bus device driver class. 这也是网上大量说的驱动驱动模型.这些的关系得熟悉得明白吧.是的.今天我先不整他们的关系.先逐个击破,然后再统一来理清楚他们之间的 ...
- 增量式PID的stm32实现(转)
源:增量式PID的stm32实现,整定过程 首先说说增量式PID的公式,这个关系到MCU算法公式的书写,实际上两个公式的写法是同一个公式变换来得,不同的是系数的差异. 资料上比较多的是: 还有一种是: ...
- el5,6,7的ntpdate服务
在el5里没有ntpdate服务 在el6里有ntpdate服务 在el7里有ntpdate服务
- 360路由器设置网段ip
路由器设置->高级设置->修改路由器地址
- HDU 2830:Matrix Swapping II(思维)
http://acm.hdu.edu.cn/showproblem.php?pid=2830 题意:-- 思路:对于每一列,它是固定的,用dp[][]处理出连续的长度.例如: 假设我们扫第四列的时候, ...
- Iphone安装铃声
PP助手 应用列表中打开铃声多多文档. 5点击铃声下载,找到下载的铃声,按下图所示步骤导出至电脑. 6在PP助手界面内,找到"视频音乐"标签,然后进入视频音乐分类下的铃声分类,点击 ...
- iOS 选择框 单选框
UIButton *checkbox = [UIButton buttonWithType:UIButtonTypeCustom]; CGRect checkboxRect = CGRectMake( ...
- 【MongoDb基础】插入数据
以mydb为事例数据库. 切换到mydb数据库. use mydb 1. insert函数. db.users.insert({name:"Derek",age:18}) 该函数会 ...
- java 之 Spring 框架(Java之负基础实战)
1.Spring是什么 相当于安卓的MVC框架,是一个开源框架.一般用于轻型或中型应用. 它的核心是控制反转(IoC)和面向切面(AOP). 主要优势是分层架构,允许选择使用哪一个组件.使用基本的Ja ...
- DDOS攻击(流量攻击)防御步骤
DDOS全名是Distributed Denial of service (分布式拒绝服务攻击),很多DOS攻击源一起攻击某台服务器就组成了DDOS攻击,DDOS 最早可追溯到1996年最初,在中国2 ...