C++中的const

95 阅读4分钟

Use const whenever possible.

"只要某值保持不变是事实,就该说出来以获得编译器的帮助" ----- 《Effective C++》

const修饰对象

可修饰global或namespace作用域下的常量,或是文件内、函数内、block内中的对象。

const修饰内置类型变量

  1. 修饰内置类型时const在类型左右是一样的,代表被修饰对象在生命周期内不可改变。
  2. 直接赋值const对象会报错,但可以通过取地址强转指针类型再修改的方式绕过这个限制,但这是一种UB,不可为之。

既可以修饰指针,也可以修饰引用,且可以指定指针即其所指物是(不是)可变的。

const修饰指针

常量指针(指向常量的指针,被指物不可变)

const出现在 * 左边(常量const, 指针*),表示被指物是不可变的,会有两种写法,效果相同 const Widget* p Widget const * p

指针常量(指针是常量,指针不可变)

const出现在 * 右边 指针是不可变的,但所指对象可变。

指针和被指物均为常量

const Widget* const p表示指针和所指对象均不可变

const作用于函数

const修饰形参

const修饰形参代表这个函数内不可对该参数进行修改

const修饰返回值

指定返回值为const,可以避免一些不必要的错误。防止使用者的错误使用造成意外。 例如返回自定义类型对象,调用方可能无意间进行错误的操作(* 解引用, 对自定义类对象进行+-等)

const修饰类成员函数

可以在类成员函数末尾使用const修饰,即void Foo::GetData() const;被const修饰的成员函数中不可修改类成员变量。 有两个作用:

  1. 表明这个函数中不会修改对象成员。
  2. 用于操作const对象。因为很多时候需要pass by reference-to-const, 因此我们需要有const成员函数来处理const对象。

const修饰成员函数的原理

我们知道,类成员函数都有一个隐含的this指针

void Foo::GetData(Foo* this)

const修饰成员函数,实际上是修饰隐含的this指针所指的对象

void Foo::GetData(const Foo* this) const

此外要注意的是,const成员函数和non-const成员函数是可以重载的。

  1. const对象只能调用const成员函数,不能调用non-const成员函数。这是因为const对象调用成员函数时传入的为const类型的this指针const A* this, 不符合non-const成员函数的参数类型A* this
  2. 同样,const成员函数不能调用non-const成员函数
  3. non-const对象/non-const成员函数可以调用const成员函数

编译器进行的是bitwise constness检查,因此const成员函数不可修改任何non-static成员变量。 与之相对的logical constness概念认为可以在客户感知不到的情况下修改对象的某些bits,这可以通过mutable关键字来实现。通过将成员变量声明为mutable,来允许const成员函数中对它进行修改(移除bitwise constness约束)。

在const 和 non-const版本的成员函数中避免重复

我们已知const和non-const的成员函数是可以重载的,为了避免代码重复,通常可以让non-const来调用const版本的成员函数。

class TextBook {
public:
	// const version
	const char& operator[](std::size_t position) const {
		// 边界检查等操作
		return text[position];
	}

	// non-const version
	char& operator[](std::size_t position) {
		return const_cast<char&>(static_cast<const TextBook&>(*this)[position]);
		//     2.移除constness	1. cast to const reference to call const operator[]            
	}
private:
	std::string text;
};

上面的non-const版本的成员函数调用了const版本,避免了代码重复。 但是要做到这一步,需要进行两步转换:

  1. 将当前对象通过static_cast转化为const类型对象static_cast<const TextBook&>*(*this)来调用const版本的函数。
  2. 将调用const版本函数得到的const char&通过const_cast移除constness, const_cast<char&>*()

实现原理

从实现上讲,const是一个编译期的语言功能,进行常量替换和赋值限制,在运行时不会对内存有限制。 但是,某些变量位于只读的内存页中,此时使用const_cast来尝试赋值会造成运行时崩溃。 具体来说,对于函数内的const变量,存放于该函数的帧中,程序拥有读写权限。 对于全局的const变量,例如static const char const data[16] = "const data";会存放于全局常量区,程序无读写权限。