摘编自 《Effective C++》 条款三。

“成员函数如果是const” 或者 “一个对象是const对象”到底意味什么?有两个流行概念:bitwise constness(又称physical constness)和 logical constness。

bitwise constness

bitwise const 阵营的人相信,成员函数只有在不更改对象之任何成员变量( static 除外)时才可以说是const。也就是说它不更改对象内的任何一个 bit。这种论点的好处是很容易侦测违反点: 编译器只需寻找成员变量的赋值动作即可。bitwise constness 正是C++对常量性( constness )的原始定义,因此const成员函数不可以更改对象内任何 non-static 成员变量。

不幸的是许多成员函数虽然不十足具备 const 性质却能通过 bitwise 测试。更具体地说,-一个更改了“指针所指物”的成员函数虽然不能算是 const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为 bitwise const 不会引发编译器异议。这导致反直观结果。假设我们有一个 CTextBlock class,用来封装C风格字符串,它将数据存储为 char* 而不是 string:

class CTextBlock {
public:
...
char& operator[](std: :size_t position) const
{ return pText [position]; } private:
char* pText;
};

这个 class 不适当地将其 operator[] 声明为const成员函数,而该函数却返回一个reference指向对象内部值。假设暂时不管这个事实,请注意,operator[] 实现代码并不更改pText。于是编译器很开心地为operator[]产出目标码。它是 bitwise const,所有编译器都这么认定。但是看看它允许发生什么事:

const CTextBlock cctb ( "Hello");1/声明一个常量对象。char* pe = &cctb[0];
//调用const operator[]取得一个指针,//指向cctb的数据。
*pc = 'J';
// cctb现在有了"Jello”这样的内容。

这其中当然不该有任何错误:你创建一个常量对象并设以某值,而且只对它调用const成员函数。但你终究还是改变了它的值。这样一来,bitwise const 好像就失去了意义。

这种情况导出所谓的 logical constness。这一派拥护者主张,一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。例如你的CTextBlock class有可能高速缓存(cache)文本区块的长度以便应付询问:

logical constness

这种情况导出所谓的 logical constness。这一派拥护者主张,一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。例如你的CTextBlock class需要回答字符串长度,当然不可能现数,要添加一个变量缓存当前长度。

class CTextB1ock {
public:
...
std::size_t length() const {
if (!lengthIsvalid){
textLength = std::strlen (pText); //错误!在const成员函数内能赋值给textLength
lengthIsvalid = true; //也不能赋值给lengthIsValid。
}
return textLength;
}
private:
char* pText;
std::size_t textLength; //最近一次计算的文本区块长度。
bool lengthIsvalid; //目前的长度是否有效。
};

length 的实现当然不是 bitwise const,因为textLength和 lengthIsValid都可能被修改。这两笔数据被修改对const CTextBlock对象而言虽然可接受,但编译器不同意。它们坚持bitwise constness。怎么办?

mutable

解决办法很简单:利用C++的一个与const语义相反的词语:mutable(可变的)。mutable释放掉n成员变量的bitwise constness 约束:

...
private:
char* pText;
mutable std::size_t textLength; //最近一次计算的文本区块长度。
mutable bool lengthIsvalid; //目前的长度是否有效。
};

从 “bitness” 来看,这个 const 函数不再是“常函数”了。但是从 “logical” 来看,我们觉得length显然没有修改字符串,那两个mutable变量只是辅助而已,修改他们不算破坏规则。

所以,有了 mutable,我们就可以定制自己的 “logical constness”,可以写明白在一个常量或者常函数里,哪些是可以改的,哪些是不能改的。

C++ mutable与常对象语义详解的更多相关文章

  1. javascript event(事件对象)详解

    javascript event(事件对象)详解   1. 事件对象     1. 事件对象 Event 对象代表事件的状态,比如事件在其中发生的元素.键盘按键的状态.鼠标的位置.鼠标按钮的状态. 什 ...

  2. 010-Scala单例对象、伴生对象实战详解

    010-Scala单例对象.伴生对象实战详解 Scala单例对象详解 函数的最后一行是返回值 子项目 Scala伴生对象代码实战 object对象的私有成员可以直接被class伴生类访问,但是不可以被 ...

  3. openerp经典收藏 对象定义详解(转载)

    对象定义详解 原文地址:http://shine-it.net/index.php/topic,2159.0.htmlhttp://blog.sina.com.cn/s/blog_57ded94e01 ...

  4. JAVA对象头详解(含32位虚拟机与64位虚拟机)

    为什么要学习Java对象头 学习Java对象头主要是为了解synchronized底层原理,synchronized锁升级过程,Java并发编程等. JAVA对象头 由于Java面向对象的思想,在JV ...

  5. 三:python 对象类型详解一:数字(上)

    一:python 的数字类型: a)整数和浮点数 b)复数 c)固定精度的十进制数 d)有理分数 e)集合 f)布尔类型 g)无穷的整数精度 h)各种数字内置函数和模块 二:各种数字类型的详解 1,数 ...

  6. CorelDRAW中如何复制对象属性详解

    复制对象属性是一种比较特殊.重要的复制方法,它可以方便而快捷地将指定对象中的轮廓笔.轮廓色.填充和文本属性通过复制的方法应用到所选对象中.本教程将详解CorelDRAW中如何复制对象属性. Corel ...

  7. 18.Java 封装详解/多态详解/类对象转型详解

    封装概述 简述 封装是面向对象的三大特征之一. 封装优点 提高代码的安全性. 提高代码的复用性. "高内聚":封装细节,便于修改内部代码,提高可维护性. "低耦合&quo ...

  8. Flex使用Blazeds与Java交互及自定义对象转换详解-DATAGRID读取ORACLE数据

    http://www.cnblogs.com/RocD-DuPeng/articles/1751040.html 一.建立Flex与Java交互的工程. 本文中讲到的交互是利用Blazeds的,因为这 ...

  9. Java对象克隆详解

    原文:http://www.cnblogs.com/Qian123/p/5710533.html 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = appl ...

  10. 六:python 对象类型详解二:字符串(下)

    一:字符串方法: 方法就是与特定对象相关联在一起的函数.从技术的角度来讲,它们是附属于对象的属性,而这些属性不过是些可调用的函数罢了.Python 首先读取对象方法,然后调用它,传递参数.如果一个方法 ...

随机推荐

  1. Cython将Numpy数组转为自定义结构体

    技术背景 前面我们写过几篇关于Cython的文章,例如Cython计算谐振势.Cython与C语言的结合.Cython调用CUDA Kernel函数.Cython有着非常Pythonic的编程范式,又 ...

  2. 一文教你如何用C代码解析一段网络数据包?【含代码】

    本文的目的是通过随机截取的一段网络数据包,然后根据协议类型来解析出这段内存. 学习本文需要掌握的基础知识: 网络协议 C语言 Linux操作 抓包工具的使用 其中抓包工具的安装和使用见下文: < ...

  3. 代码随想录Day19

    235. 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 p.q,最近公共祖先表示为一个 ...

  4. 使用Kiota工具生成WebApi的代理类,以及接口调用的简单体验

    前言 当前.NET环境下,生成WebApi代理类的工具已经有很多选择了,比如OpenApi Generator,NSwag和Refitter等,不同的工具生成的代码风格以及实现方式略有不同,比如Ref ...

  5. 【原创】VSCode 快捷键汇总(已整理成一张 A4 纸大小,方便打印)

    由于经常记不住 VSCode 的快捷键,每次查找不方便,于是乎做了一份 VSCode 快捷键汇总,已整理成一张 A4 纸大小,方便打印,这样查找就直观多了~ 直接保存图片,横向打印即可.

  6. LaTeX hyperref 包去掉链接方框

    \usepackage[hidelinks]{hyperref}

  7. Kubernetes-19:Prometheus-operator集群监控神器

    Prometheus-operator集群监控 github地址:https://github.com/prometheus-operator/kube-prometheus 具体的Prometheu ...

  8. 使用Golang的协程竟然变慢了|100万个协程的归并排序耗时分析

    前言 这篇文章将用三个版本的归并排序,为大家分析使用协程排序的时间开销(被排序的切片长度由128到1000w) 本期demo地址:https://github.com/BaiZe1998/go-lea ...

  9. CEIT算法训练-双指针部分题解(全12题)

    代码宏定义以及框架约定 #include <bits/stdc++.h> using namespace std; #define IOS ios_base::sync_with_stdi ...

  10. android 反编译APK取源代码。

    坑,自己写的Android APK 程序,发现线上版本是 1.9.4 ,本地的代码版本却是 1.9.1.不知道到底怎么回事,svn里面也没有日志记录.....只能从线上apk反编译来看看了,幸好这个升 ...