前言
最近在读《Effective C++》,对里面的思想和代码深有感触,因此在此做点记录并加以自己的理解,方便以后查看。
本文内容来自条款03:尽可能使用Const (Use const whenever possible)
。
Const成员函数
介绍
将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。这一类成员函数之所以重要,基于以下两点:
- 它们使class接口比较容易被理解:因为知道哪个函数可以改动对象而哪个函数不行,很是重要。
- 它们使"操作const对象"成为可能:这对编写高效代码很关键,因为改善C++程序效率的一个根本方法就是以const引用(pass by reference-to-const)方式传递对象,而此技术的前提是我们有const成员函数可以用来处理取得的const对象。
Const成员函数的两种概念
- bitwise constness:又称(Physical constness),即成员函数只有在不更改对象内的任何一个bit时,才叫const。这正是C++对常量性(constness)的定义,因此成员函数不可以更改对象内任何non-static成员变量。
- logical constness:逻辑上的const,在某些情况下,const成员函数满足bitwise constness,但从逻辑上却是改变了对象的成员变量。因此这种情况导出了logical constness,这一派系主张,一个const成员函数可以修改它锁处理的对象内的某些bits,但只有在客户端侦测不出的情况写才能如此。
- 示例:满足bitwise constness不满足logical constness的情况
class CTextBlock{ public: //... char& operator[] (std::size_t position) const{//bitwise const声明,但其实不满足逻辑上的const return pText[position];//返回了指针所指向的数据引用,并不会改变指针本身, //但却有可能改变指针指向的空间的数据,因此不满足逻辑上的const } private: char* pText; } int main(){ const CTextBlock cctb("Hello"); char *pc = &cctb[0]; //取的cctb[0]的引用,再获取地址 *pc = 'J'; //cctb现在变成了"Jello" return 0; }
特性
两个成员函数如果只是常量性(constness)不同,是可以被重载的。具体请看示例。
示例1
问题
如何解决const成员函数的bitwise constness约束以实现logical constness?
情景
CTextBlock 可能高速缓存文本区块的长度以便应付查询
实现
因此我们可能写出这样的代码:
class CTextBlock {
public:
//...其他内容
std::size_t length() const;
private:
char *pText;
std::size_t textLength; //最近一次计算的文本区块长度
bool lengthIsValid; //目前的长度是否有效
};
std::size_t CTextBlock :: length() const {
if (!lengthIsValid) {
textLength = std::strlen(pText);//错误,在const函数内,不能修改对象数据,是要求bitwise const的
lengthIsValid = true;
}
return textLength;
}
但这样length函数的实现并不是bitwise const,因为textLength和lengthIsValid都可能被修改,编译器不会通过。
解决办法:利用mutable(可变的)关键字释放掉non-static成员变量的bitwise constness约束。
/*
情景:CTextBlock 可能高速缓存文本区块的长度以便应付查询
功能:借用mutable实现logical const而非bitwise const
mutable关键字:mutable是c++的一个与const相关的摆动场,意为可变的,
因此mutable可以释放掉non-static成员变量的bitwise constness约束
缺点:并不能解决所有的const难题
*/
class CTextBlock {
public:
//...其他内容
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength; //最近一次计算的文本区块长度
mutable bool lengthIsValid; //目前的长度是否有效
};
std::size_t CTextBlock::length() const {
if (!lengthIsValid) {
textLength = std::strlen(pText);
lengthIsValid = true;
}
return textLength;
}
示例2
问题
在const和non-const成员函数中如何避免重复?
情景
TextBlock内的operator[]不单只返回一个reference to char,还会进行边界检测,访问记录,数据校验等。
实现
class TextBlock {
public:
//...其他内容
const char& operator[](std::size_t pos) const {
//...边界检验
//...记录数据访问(log)
//...检验数据完整性
return text[pos];//返回数据
}
//这样两个函数除了返回值得不同,没有什么区别,重复率太高
char& operator[](std::size_t pos) {
//...边界检验
//...记录数据访问(log)
//...检验数据完整性
return text[pos];//返回数据
}
private:
std::string text;
};
这两个函数函数体一模一样,如何避免重复呢?可选的方法是:通过转型,将*this转为const去调用const的operator[]版本,并得到一个const reference to char,并去掉const约束后返回
,因此得到以下代码:
class TextBlock {
public:
//...其他内容
const char& operator[](std::size_t pos) const {
//...边界检验
//...记录数据访问(log)
//...检验数据完整性
return text[pos];//返回数据
}
char& operator[](std::size_t pos) {
return const_cast<char&>(
static_cast<const TextBlock&>(*this)[pos]
);
}
private:
std::string text;
};
注意: 此处不能反过来做,即不能在const的operator[]去掉*this的const约束再调用non-const 的 operator[]函数得到char&的返回值再转为const char&,const函数内部是不能对对象进行修改的。
总结
- 编译器使用的常量性定义时bitwise constness的,但我们编写程序时,应该使用“概念上的常量性”。
- 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
参考文献
《Effective C++(第三版)》