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. C#的循环语句(四)

    一.while 循环(1).while 其实是for循环的变形写法for(int i = 1; i<=5;i++)  {循环体:} 上面的for循环可以写成int i= 1:for(;i< ...

  2. H3C ACL包过滤的局限性

  3. PHP Warning: Module 'json' already loaded in Unknown on line 0

    Zend加密文件.启动服务器后无法正常运行加密后的文件.处理方法:   某台服务器上跑着php 5.3.8,线上服务运行正常. 某天拆分php错误日志时,发现其中有一段连续的错误信息. [14-Feb ...

  4. 安装vue-cli和安装nuxt

    安装vue-cli:1.npm install vue-cli -g2.vue install webpack 项目名3.cd 项目名4.npm install5.npm i webpack-dev- ...

  5. Vue的Router路由传参

    一.文件结构 二.vue.js 打开此链接 https://cdn.bootcss.com/vue/2.6.10/vue.js 复制粘贴页面的所有内容 三.vue-router.js 打开此链接  h ...

  6. P1084 骑士的工作

    题目描述 你作为一个村的村长,保卫村庄是理所当然的了.今天,村庄里来了一只恶龙,他有n个头,恶龙到处杀人放火.你着急了.不过天无绝人之路,现在来了一个骑士团.里面有m位成员,每个人都可以砍掉一个大小不 ...

  7. CF1146G Zoning Restrictions

    CF1146G Zoning Restrictions 网络流 h<=50? 直接都选择最大的,ans=n*h*h 最小割 考虑舍弃或者罚款 有一个>x就要罚款? 经典取值限制的模型:切糕 ...

  8. es6笔记 day3---对象简介语法以及对象新增

    以前的老写法↓ 新写法来了↓ 提示:千万不要手贱,在里面去用箭头函数!!! -------------------------------------------------------------- ...

  9. 【hdu 1849】Rabbit and Grass

    Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s) ...

  10. Nginx与PHP交互过程 + Nginx与PHP通信的两种方式

    一.Nginx与PHP交互过程的7步走(用户对动态PHP网页访问过程) step1:用户将http请求发送给nginx服务器(用户和nginx服务器进行三次握手进行TCP连接) step2:nginx ...