条款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 ...
随机推荐
- SuperSocket命令和命令加载器
关键字: 命令, 命令加载器, 多命令程序集 命令 (Command) SuperSocket 中的命令设计出来是为了处理来自客户端的请求的, 它在业务逻辑处理之中起到了很重要的作用. 命令类必须实现 ...
- cf1234-div3
A 水题 B 直接看2,发现`unordered_map被卡了...` 乖乖离散化 C 有六种水管,可以任意的旋转,使得有一条从(1, 0)到(2, n)的通路. 找规律,当时写D没来得及看 #inc ...
- Python数据可视化matplotlib和seaborn
Python在数据科学中的地位,不仅仅是因为numpy, scipy, pandas, scikit-learn这些高效易用.接口统一的科学计算包,其强大的数据可视化工具也是重要组成部分.在Pytho ...
- Mule ESB 自带例子hello初体验
1 配置的流的效果图 2 应用配置文件hello.xml内容 <?xml version="1.0" encoding="UTF-8"?> < ...
- eclipse中maven报错--Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match.
-Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable a ...
- vue-learning:34 - component - 内置组件 - 动态组件component 和 is属性
component动态组件 / is属性 让多个组件使用同一个挂载点,并动态切换,这就是动态组件. 必要条件: 组件标签使用<component></component> 动态 ...
- 2019-8-31-dotnet-Framework-源代码-·-Ink
title author date CreateTime categories dotnet Framework 源代码 · Ink lindexi 2019-08-31 16:55:58 +0800 ...
- vue-learning:19 - js - filters
filters 基本使用 仅限在插值{{}}和v-bind指令中使用 管道符|分隔 链式调用 传入参数 全局注册和局部注册 纯函数性质(不能使用this) 基本使用 我们看下之前用计算属性实现的例子, ...
- SPOJ - REPEATS Repeats (后缀数组)
A string s is called an (k,l)-repeat if s is obtained by concatenating k>=1 times some seed strin ...
- CodeForces - 922D Robot Vacuum Cleaner (贪心)
Pushok the dog has been chasing Imp for a few hours already. Fortunately, Imp knows that Pushok is a ...