条款03:尽可能使用const

   const 允许你告诉编译器和其他程序员某值应该保持不变。
  关键字const 多才多艺。 第一, 你可以用它在class外部修饰global或namespace(见条款2)作用域中的变量, 或修饰文件、函数、或区块作用域(block scope)中被声明为static 的对象。 第二, 你可以用它修饰class 内部的static和non-static成员变量。 对于指针, 你可以指出指针自身、指针所指物, 或者两者都(或都不)是const:

char greeting[] = "Hello"; // non-const pointer, non-const data
char *p = greeting; // non-const pointer, const data
const char *p = greeting; // const pointer, non-const data
const char* const p = greeting; // const pointer, const data

const 出现在星号左边, 表示被指物是常量; 如果出现在星号右边, 表示指针自身是常量,; 如果出现在星号两边, 表示被指物和指针两者都是常量。
   如果被指物是常量,const写在类型之前, 或者写在类型之后、星号之前。这下面两种写法的意义相同。

void f1(const Widget* pw); // f1 获得一个指针, 指向一个常量的(不变的)Widget对象
void f2(Widget const * pw); // f2 和f1 同

   STL迭代器系以指针为根据被造出来, 所以迭代器的作用就像个T* 指针。 声明 迭代器为const 就像声明指针为const 一样(即声明一个T* const指针), 表示这个迭代器不得指向不同的东西, 但它所指的东西的值是可以改动的。 如果你希望迭代器所指的东西不可被移动(即希望STL模拟一个const T * 指针), 你需要的是const_iterator:

std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin(); // iter 的作用像个T * const
*iter = 10;	// 没问题, 改变iter所指物
++iter; // 错误! iter是const
std::vector<int>::const_iterator cIter = vec.begin();  // cIter的作用像个const T *
*cIter = 10; // 错误! *cIter是const
++cIter;// 没问题, 改变cIter

   const 最具威力的用法是面对函数声明时的应用。 在一个函数声明内, const 可以和函数的返回值、各参数、函数自身(如果是成员函数)产生关联。

   令函数返回一个常量值, 可以降低因客户错误而造成的意外, 而又不至于放弃安全性和高效性。 例如, 有理数(rational numbers, 条款24)的operator* 声明式:

class Rational {...};
const Rational operator*(const Rational &lhs, const Rational & rhs);

为什么返回一个const对象? 原因式如果不这样客户就能实现这样的暴行:

Rational a, b, c;
(a * b) = c; // 在 a*b的结果上调用operator= 

   我不知道为什么会有人想对两个数值的乘积再做一次赋值(assignment), 但我知道许多程序员会在无意识中那么做, 只因为单纯的打字错误(以及一个可被隐式转换为bool的类型):

if (a *b = c) ...

   如果 a和b都是内置类型, 这样的代码直接了当就是不合法。 而一个"良好的用户自定义类型"的特征是它们避免无端地与内置类型不兼容(见条款 18), 因此允许对两值乘积做赋值动作也就没什么意思了。 将operator*的回传值声明为const 可以预防那个"没意思的赋值动作", 这就是该那么做的原因。

const 成员函数

  将const实施于成员函数的目的, 是为了确认该成员函数可作用于const对象身上。 这一类成员函数之所以重要, 基于两个理由。
  第一, 它们使class接口比较容易被理解。 得知哪个函数可以改动对象内容而哪个函数不行。
  第二, 它们使“操作const对象”成为可能。 因为如条款20所言, 改善C++程序效率的一个根本办法是以pass by reference-to-const方式传递对象, 而此技术可行的前提是, 我们有const成员函数可用来处理取得(并经修饰而成)的const对象。

class TextBlock{
public:
	const char & operator[](std::size_t position) const // operator[] for
	{
		return text[position];							// const 对象
	} 
	char & operator[](std::size_t position) {			// operator[] for 
		return text[position];							// non-const 对象
	}
private:
	std::string text{};
};

// TextBlock的operator[]s可被这么使用:
TextBlock tb("Hello"); 
std::cout << tb[0];  // 调用non-const TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0];  // 调用 const TextBlock::operator[]

真实程序中const 对象 大多用于passed by pointer-to-const 或 passed by reference-to-const 的传递结果。 所以下面比较真实:

void print(const TextBlock & ctb) { // 此函数ctb是const
	std::cout << ctb[0]; 	// 调用 const TextBlock::operator[]
}

   只要重载operator[] 并对不同版本给予不同的返回类型, 就可以令const 和 non-const TextBlocks获得不同的处理:

std::cout << tb[0];  // 读一个non-const TextBlock
tb[0] = 'x'; 	// 写一个non-const TextBlock
std::cout << ctb[0]; // 读一个const TextBlock
ctb[0] = 'x'; // 错误! 写一个const TextBlock

上述错误只因operator[] 的返回类型以致, 至于 operator[] 调用动作自身没问题。 错误起因于企图对一个"由 const 版之operator[] 返回"的const char & 施行赋值动作。
   non-const operator[]的返回类型是个reference to char, 不是 char。 如果 operator[]只是返回一个char, 下面这样的句子就无法通过编译:
tb[0] = ‘x’;
   如果函数的返回类型是个内置类型, 那么改动函数返回值从来就不合法。 纵使合法, C++以by value 返回对象这一事实(见条款20)意味被改动的其实是tb.text[0] 的一个副本, 不是tb.text[0]自身, 那不会是你想要的行为。
   成员函数如果是const 意味什么? 有两个流行概念: bitwise constness(又称physical constness) 和 logical constness。
 bitwise const 阵营的人相信, 成员函数只有在不更改对象的任何变量(static)时才可以说是const。 也就是说它不更改对象内的任何一个bit。这种论点的好处是很容易侦测违反点: 编译器只需寻找成员变量的赋值动作就行。 bitwise constness 正是C++ 对常量性(constness)的定义, 因此const 成员函数不可以更改对象内任何non-static成员变量。
  不幸的是许多成员函数虽然不十足具备const性能却能通过bitwise测试。 更具体地说, 一个更改了"指针所指物"的成员函数虽然不算是const, 但如果只有指针(而非其所指物)隶属于对象, 那么称此函函数为bitwise const 不会引发编译器异议。 这导致反直观的结果。 假设我们有一个TextBlock-like class, 它将数据存储为char *而不是string, 因为它需要和一个不认识string对象的 C API沟通:

class CTextBlock {
public:
	char & operator[](std::size_t position) const // bitwise const声明, 但其实不适当
	{																
		return pText[position];
	}
private:
	char * pText{};
};

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

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

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

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成员函数内不能赋值给textLength和lengthIsValid
   		lenghIsValid = true;
   	}
   	return textLength;
}

length 的实现当然不是bitwise const, 因为textLength和lengthIsValid都有可能被修改。 这两笔数据被修改对const CTextBlock对象而言虽然可接受, 但编译器不同意。 它们坚持bitwise constness。 解决方式: 利用 C+的一个与const相关的摆动场:mutable(可变的)。mutable释放掉non-static成员变量的bitwise constness约束:

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); // 错误!在const成员函数内不能赋值给textLength和lengthIsValid
   		lenghIsValid = true;
   	}
   	return textLength;
}
在 const 和 non-const 成员函数中避免重复
class TextBlock {
public:
	const char & operator[](std::size_t position) const
	{
		// bounds checking
		// log access data
		// verify data integrity
		return text[position];
	}
	char & operator[] (std::size_t position) {
		// bounds checking
		// log access data
		// verify data integrity
		return text[position];
	}
private:
	std::string text;
};
class TextBlock {
public:
	const char & operator[](std::size_t position) const
	{
		// bounds checking
		// log access data
		// verify data integrity
		return text[position];
	}
	
	char & operator[] (std::size_t position) {
		return const_cast<char&>(
		static_cast<const TextBlock&>(*this)[position]);
	}
private:
	std::string text;
};

总结:

  1. 将某些东西声明为const 可帮助编译器侦测出错误用法。 const 可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  2. 编译器强制实施bitwise constness, 但你编写程序时应该使用“概念上的常量性”。
  3. 当const 和 non-const 成员函数有着实质等价的实现时, 令non-const 版本调用const 版本可避免代码重复。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值