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的更多相关文章

  1. Effective C++ -----条款03:尽可能使用const

    如果关键字const出现在星号左边,表示被指物是常量:如果出现在星号右边,表示指针自身是常量:如果出现在星号两边,表示被指物和指针两者都是常量. char greeting[] = " he ...

  2. 《Effective C++》读书笔记 条款03 尽可能使用const 使代码更加健壮

    如果你对const足够了解,只需记住以下结论即可: 将某些东西声明为const可帮助编译器侦测出错误用法,const可被施加于任何作用于内的对象.函数参数.函数返回类型.成员函数本体. 编译器强制实施 ...

  3. 条款03 尽可能使用const

    一.概述 使用const约束对象:可以获得编译器的帮助(指出相关出错的地方) const与成员函数:const重载.转型.避免代码重复 二.细节 1. 为什么有些函数要返回const对象(看上去没必要 ...

  4. Effective C++_笔记_条款03_尽可能使用const

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 关键字const多才多艺,语法变化多端.关于const的基本用法 ...

  5. Effective C++ 条款三 尽可能使用const

    参考资料:http://blog.csdn.net/bizhu12/article/details/6672723      const的常用用法小结 1.用于定义常量变量,这样这个变量在后面就不可以 ...

  6. 条款21: 尽可能使用const

    对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const,还有,两者都不指定为const: char *p = "hello"; ...

  7. 《Effective C++ 》学习笔记——条款03

    ***************************************转载请注明出处:http://blog.csdn.net/lttree************************** ...

  8. Effective C++ 之 Item 3:尽可能使用 const

    Effective C++ Chapter 1. 让自己习惯C++(Accustoming Yourself to C++) Item 3. 尽可能使用 const (Use const whenev ...

  9. Effective C++ 条款03:尽可能使用const

    场景一 用于修饰指针 char greeting[] = "Hello"; char* p = greeting; // non-const pointer, non-const ...

随机推荐

  1. 如何查看redis内存使用情况

    https://jingyan.baidu.com/article/2c8c281dbd079f0008252a0f.html Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以 ...

  2. IE下form表单密码输入框可以输入中文问题

    今天遇到了一个问题: 在IE浏览器登录界面,密码输入框,切换到中文输入法,竟然可以输入中文,已经设置过了input的type="password". 解决方法: 可以给input设 ...

  3. PyTorch官方中文文档:torch.optim 优化器参数

    内容预览: step(closure) 进行单次优化 (参数更新). 参数: closure (callable) –...~ 参数: params (iterable) – 待优化参数的iterab ...

  4. 最小生成树prim、

    过年那几天确实没好好学习.在老家闲着也是闲着.可是就是没看书. 回来这几天又一直在弄个人博客.买域名云服务器备案什么的- -. 麻烦死了呢. 在腾讯花1块钱备案了一个网站www.goodgoodstu ...

  5. java 获得Class对象

    如何得到各个字节码对应的实例对象? 每个类被加载后,系统会为该类生成对应的Class对象,通过Class对象可以访问到JVM中的这个类, 3种方式: 1.调用某个类的class属性获取Class对象, ...

  6. linux进程简单睡眠

    当一个进程睡眠, 它这样做以期望某些条件在以后会成真. 如我们之前注意到的, 任何睡 眠的进程必须在它再次醒来时检查来确保它在等待的条件真正为真. Linux 内核中睡眠的 最简单方式是一个宏定义, ...

  7. JS异步解决方案

    前言 异步最早的解决方案是回调函数,如ajax,事件的回调,setInterval/setTimeout中的回调.但是回调函数有回调地狱的问题; 为了解决回调地狱的问题,社区提出了Promise解决方 ...

  8. Android6_大致了解4大组件

    一.Activity和View Activity是Android应用中负责与用户交互的组件. View是所有UI控件.容器控件的基类.View组件就是Android应用中用户实实在在看到的部分. Ac ...

  9. AI炼丹 - 深度学习必备库 numpy

    目录 深度学习必备库 - Numpy 1. 基础数据结构ndarray数组 1.1 为什么引入ndarray数组 1.2 如何创建ndarray数组 1.3 ndarray 数组的基本运算 1.4 n ...

  10. .NETCore3.1中的Json互操作最全解读-收藏级

    前言 本文比较长,我建议大家先点赞.收藏后慢慢阅读,点赞再看,形成习惯! 我很高兴,.NETCore终于来到了3.1LTS版本,并且将支持3年,我们也准备让部分业务迁移到3.1上面,不过很快我们就遇到 ...