条款03:尽可能使用const
1. 总结
- const可用于任何作用域内的对象、函数参数、函数返回值、成员函数自身,将这些内容声明为const可帮助编译器侦测出错误用法
- 对于const成员函数,C++编译器强制要求bitwise constness,但在编写程序时应该使用"概念上的常量性"
- const成员函数可以修改被mutable关键字修饰的non-static成员变量
- 当const和non-const成员函数有着实质等价的函数体时,令non-const版本调用const版本可避免代码重复,但绝对不能反过来调用
2. const对象
关键字const可以用于以下对象。
- 在class外部修饰global或namespace作用域中的常量
- 修饰区块作用域(block scope)中被声明为static的对象
- 修饰class内部的static和non-static成员变量
- 面对指针,根据“左数右指”口诀,可以指出指针自身、指针所指数据,或两者都是(或都不是)const
STL迭代器以指针为根据塑模出来,所以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; //没问题
++iter; //错误,iter不可修改
std::vector<int>::const_iterator const_iter = vec.begin(); //const_iter相当于const T *指针
*const_iter = 10; //错误,*const_iter不可修改
++const_iter; //没问题
3. const函数返回值和函数参数
const最具威力的用法是面对函数声明时的应用。在一个函数声明中,const可以和函数参数、函数返回值、函数自身(如果是成员函数)产生关联。
const用于函数参数只需记住一条原则:除非函数体中需要改动参数,否则就将它们声明为const。
const用于函数返回值,往往可以降低因使用错误而造成的意外,同时又不至于放弃安全性和高效性,举个例子,看下面有理数类operator *的声明。
class Rational { ... };
const Rational operator * (const Rational &lhs, const Rational &rhs);
Rational a, b, c;
(a * b) = c; //有意错误,对两个数的乘积进行赋值,就好比1 = 2一样
if (a * b = c) //无意错误,将==漏写为=
- 如果a、b、c都是内置类型,上述代码直接就是不合法
- 而对于重载了operator *的class,由于operator =的存在,如果不对返回值使用const,编译器就不会报错
- 然而,一个良好的用户自定义类型的特征是避免它们无端地与内置类型不兼容(见条款18)
- 因此,将operator *的返回值声明为const,就可以让编译器检测出这种错误用法
4. const成员函数
const成员函数的重要性
将const用于成员函数的目的,是为了确认该成员函数可作用于const对象身上。const成员函数之所以重要,基于两个理由。
- 它们使class接口比较容易被理解,可以很明显的得知哪些函数可以改动对象而哪些函数不能
- 它们使操作const对象成为可能
第2条对于编写高效代码是个关键,因为如条款20所述,改善C++程序效率的一个根本方法是以const引用的方式传递对象。
const引用的可能是const对象,而const对象只能调用const成员函数。
所以此技术可行的前提是,有const成员函数可用来处理取得的const对象;否则,就算能将const对象传进来,也没有办法去处理它。
C++有一个重要特性:两个成员函数如果只是常量性不同,则可以构成重载,即使它们的参数类型、参数个数、参数顺序都完全一致。
基于这个特性,再结合上面提到的高效编码技巧,就可以得出如下所示的接口设计。
class TextBlock
{
private:
std::string text;
public:
//用于const对象,由于const对象内容不允许修改,因此返回值也加了const
const char &operator [] (std::size_t postion) const
{
return text[postion];
}
//用于non-const对象
char &operator [] (std::size_t postion)
{
return text[postion];
}
};
void print(const TextBlock &text)
{
std::cout << text[0];
}
TextBlock text;
const TextBlock const_text;
print(text); //调用char &operator [] ()
print(const_text); //调用const char &operator [] () const
bitwise constness
C++编译器要求const成员函数不能更改对象内的任何non-static成员变量,简单地说就是const成员函数中不能出现对non-static成员变量的赋值操作。
这种要求实质上是不能更改对象内的任何一个bit,因此叫做bitwise constness。
不幸的是,许多const成员函数虽然不完全具备const性质,但却能通过C++编译器的bitwise检验,更具体地说,就是:
- 从"概念上的常量性"来看,一个更改了指针指向数据的成员函数不能算是const成员函数
- 但如果只有指针隶属于对象,那么称此函数为bitwise constness不会引发编译器异议
class TextBlock
{
private:
char *pText;
public:
char &operator [] (std::size_t postion) const
{
return pText[postion];
}
};
const TextBlock text("Hello"); //声明一个const对象
char *pc = &text[0]; //调用const char &operator []取得一个指针,指向text的数据
*pc = 'J'; //通过pc指针将text的数据改为了"Jello"
上面这个class将operator []声明为const成员函数,但却返回了一个reference指向对象内部数据,这种做法是错误的,条款28对此有深刻讨论,我们暂时先忽略它。
从编译器bitwise constness的角度看,上述代码不存在任何问题,但你终究还是改变了const对象的值,这种情况导出所谓的logical constness。
logical constness
logical constness指的是,const成员函数可以修改它所处理对象内的某些bits,但前提是用户察觉不到这种修改。
要想在const成员函数中修改non-static成员变量,需要对这些成员变量使用mutable关键字,mutable可以去除non-static成员变量的bitwise constness约束。
class CTextBlock
{
private:
char *pText;
mutable std::size_t textLength; //最近一次计算的文本长度
mutable bool lengthIsValid; //目前的长度是否有效
public:
std::size_t length() const;
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid)
{
textLength = std::strlen(pText);
lengthIsValid = true;
}
return textLength;
}
length()的实现当然不是bitwise constness,因为textLength和lengthIsValid都可能被修改,但这两个成员变量被修改对于const CTextBlock对象是可以接受的。
5. 在const和non-const成员函数中避免重复
现在我们对class TextBlock做一些修改,假设operator []不单只是返回一个reference指向某字符,还执行边界检查、日志数据访问、数据完整性检验等工作。
class TextBlock
{
private:
std::string text;
public:
//用于const对象,由于const对象内容不允许修改,因此返回值也加了const
const char &operator [] (std::size_t postion) const
{
... //边界检查
... //日志数据访问
... //数据完整性检验
return text[postion];
}
//用于non-const对象
char &operator [] (std::size_t postion)
{
... //边界检查
... //日志数据访问
... //数据完整性检验
return text[postion];
}
};
operator[]的const和non-const版本中的代码重复,可能会随着编译时间、持续维护、代码膨胀等因素而成为令人头痛的问题。
将重复代码封装到一个private函数中,并分别在两个函数中调用它,不失为一个解决该问题的好办法,但依然存在代码重复,如函数调用、return语句。
真正最好的办法是:先实现operator []的const版本,然后在non-const版本中调用它。如下示例代码所示,这种方法有两个技术要点。
- 先使用static_cast为*this添加const属性
- 接下来调用const版本成员函数,并使用const_cast去除返回值中的const,最后作为non-const函数的返回值返回
class TextBlock
{
private:
std::string text;
public:
//用于const对象,由于const对象内容不允许修改,因此返回值也加了const
const char &operator [] (std::size_t postion) const
{
... //边界检查
... //日志数据访问
... //数据完整性检验
return text[postion];
}
//用于non-const对象
char &operator [] (std::size_t postion)
{
const TextBlock &const_this = static_cast<const TextBlock &>(*this); //将自身从TextBlock &转换为const TextBlock &
return const_cast<char &>(const_this[postion]); //调用const版本的operator [],并去除返回值中的const属性,然后返回
}
};
注意,千万不要令const版本调用ono-const版本来避免代码重复,因为const版本调用non-const版本的唯一方法是去除自身的const属性,这绝对不是个好事情。
条款03:尽可能使用const的更多相关文章
- Effective C++ -----条款03:尽可能使用const
如果关键字const出现在星号左边,表示被指物是常量:如果出现在星号右边,表示指针自身是常量:如果出现在星号两边,表示被指物和指针两者都是常量. char greeting[] = " he ...
- 《Effective C++》读书笔记 条款03 尽可能使用const 使代码更加健壮
如果你对const足够了解,只需记住以下结论即可: 将某些东西声明为const可帮助编译器侦测出错误用法,const可被施加于任何作用于内的对象.函数参数.函数返回类型.成员函数本体. 编译器强制实施 ...
- 条款03 尽可能使用const
一.概述 使用const约束对象:可以获得编译器的帮助(指出相关出错的地方) const与成员函数:const重载.转型.避免代码重复 二.细节 1. 为什么有些函数要返回const对象(看上去没必要 ...
- Effective C++_笔记_条款03_尽可能使用const
(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 关键字const多才多艺,语法变化多端.关于const的基本用法 ...
- Effective C++ 条款三 尽可能使用const
参考资料:http://blog.csdn.net/bizhu12/article/details/6672723 const的常用用法小结 1.用于定义常量变量,这样这个变量在后面就不可以 ...
- 条款21: 尽可能使用const
对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const,还有,两者都不指定为const: char *p = "hello"; ...
- 《Effective C++ 》学习笔记——条款03
***************************************转载请注明出处:http://blog.csdn.net/lttree************************** ...
- Effective C++ 之 Item 3:尽可能使用 const
Effective C++ Chapter 1. 让自己习惯C++(Accustoming Yourself to C++) Item 3. 尽可能使用 const (Use const whenev ...
- Effective C++ 条款03:尽可能使用const
场景一 用于修饰指针 char greeting[] = "Hello"; char* p = greeting; // non-const pointer, non-const ...
随机推荐
- 推荐几个web前端比较实用的网站
第一次写博客,说实在的有点紧张和兴奋,哈哈哈哈,本人工作了有两年的时间,平时也有做笔记的习惯,但是都做得乱七八糟的,所以就想通过写博客来记录.好了,废话不多说了,先来几个觉得在工作中使用到的,还不错的 ...
- win10访问Microsoft数据库问题总结
今天突然接到任务 把15年的一个wpf项目倒腾出来,根据客户要求微调界面效果 翻扯项目历史记录,找到最后一版的项目,不过历经三载,开发时的环境和现在的环境略有差距 原来:win7 64位 vs20 ...
- 【t065】最敏捷的机器人
Time Limit: 1 second Memory Limit: 128 MB [问题描述] [背景] Wind设计了很多机器人.但是它们都认为自己是最强的,于是,一场比赛开始了~ [问题描述] ...
- Python __call__详解
可以调用的对象 关于 __call__ 方法,不得不先提到一个概念,就是可调用对象(callable),我们平时自定义的函数.内置函数和类都属于可调用对象,但凡是可以把一对括号()应用到某个对象身上都 ...
- Python--day40--主线程和子线程代码讲解
1,最简单的线程例子: 2,多线程并发: import time from threading import Thread #多线程并发 def func(n): time.sleep(1) prin ...
- 4-10 items设计
1,items相当于dict,但是又比字典好 2,parse.urljoin(response.url,post_url)方法,其中image_url是一个域名的话,其中的当前域名就不用再添加. yi ...
- 在spring security3中使用自定义的MD5和salt进行加密
首先看代码: <authentication-manager alias="authenticationManager"> <authentication-pro ...
- P1007 N钱M鸡问题
题目描述 已知公鸡 \(5\) 元钱一只,母鸡 \(3\) 元钱一只,小鸡 \(3\) 只 \(1\) 元钱. 告诉你一个整数 \(n(1 \le n \le 1000)\) ,你现在要花 \(n\) ...
- SpringBoot 上传文件到linux服务器 异常java.io.FileNotFoundException: /tmp/tomcat.50898……解决方案
SpringBoot 上传文件到linux服务器报错java.io.FileNotFoundException: /tmp/tomcat.50898-- 报错原因: 解决方法 java.io.IOEx ...
- CSS选择器权重计算规则
从CSS代码存放位置看权重优先级:内嵌样式 > 内部样式表 > 外联样式表.其实这个基本可以忽视之,大部分情况下CSS代码都是使用外联样式表. 从样式选择器看权重优先级:important ...