Const in C++

1. Const as a Promise

现代C++中,我们会用const来来做以下的三件事情:

  1. 定义一个常符号/常量
  2. 传递一个不可修改的参数
  3. 定义常成员函数以防止潜在的修改

对于第一种,constexpr 通常是更好的选择。对于 const 而言,最主要的作用就是用指针或引用传递参数、返回值时防止修改指令 (modifiable operand) 对参数和返回值的修改。防止修改指令对参数/返回值的修改,实际上就是将潜在的运行时 bugs 转换成了编译时的一些错误。而通常情况下,编译时错误很难被忽略,所以使用 const 可以让我们更好地创建接口 (interfaces) 。

constexpr 的职责是将运行时的一些计算转换成编译时计算。

2. The Non-Modifiable One

在使用const的时候,我们经常看到:

int const number = 123456;
char const msg[] = "hello";

上面的示例中,我们使用const来定义一些不可修改的类型,第一个定义了一个不可修改的int类型,下面定义了一个用const char所组成的array。这些类型的共同点是可读但不可修改(写)。

2.1 Mandated Initialization

这些不可修改的类型只有在初始化的时候给初值,并且C++要求const类型的对象必须初始化。

namespace example{
	int const un_initialized_;  // Error: missing initializer.
	extern int const initialized_;  // Ok: this is a declaration, not a def
}

2.2 Constant Expressions

Constexpr is conster than const.

在 C/C++ 中,在 array 中对维度的定义也必须是一个常值,叫做 integer constant expression。

int x[n];  // Error
int y[10]; // OK

int const level = 10;
int x[2 * level + 1];  // OK

在定义一个位域长度时也是一样的。

int bf: W; // Firld width, W must be constant.

C++允许你将一个非const对象用const来初始化。下面的程序允许你在运行时初始化变量 level,也因此,在编译时的level不是一个const object,对应的表达式也不再是constant expression。

int n = 10;
int const level = n;  // Ok
int x[2 * level + 1]; // Error, this is not a constant expression

constexpr 就是在这种情况下诞生的,一个 constexpr 的对象必须用一个 constant expression 来初始化:

int n = 10;
constexpr int m = n; // Not ok, n is not a constant expression.
constexpr int l = 10; // Ok, 10 is a const expression.

2.3 Declaration Specifiers and Declarators

每个对象和函数的声明都有两个部分:declaration specifiersdeclarator。在下面的例子中前面一长串static unsigned long int都是declaration specifiers,后面的*x[N]是declarator。

static unsigned long int *x[N];

Declaration specifiers进一步又分为type specifier(intunsignedlong等)和non-type specifier(externstaticinline等)。

2.3.1 Declarator

Declarator是一个declarator-id,围绕着一些operators。上面的例子中,x就是这个declarator-id,*[]都属于operator。这些operators有一定的优先级关系。

Precedence Operator Meaning
Highest ( ) Grouping
[ ] Array
( ) Function
Lowest * Pointer
& Lvalue ref
&& Rvalue ref

根据优先级关系,我们能够轻易说出后面的*x[N]是一个array of pointers。而我们很容易能够想清楚,(*x)[N]就是一个指针指向一个array of N integers。

2.3.2 Declaration Specifiers

我们提到过,Declaration specifiers进一步又分为type specifier和non-type specifier。它们分别的作用是什么?假如我们有下面的声明:

static unsigned long int *x[N];

其中static是修饰declarator-idx的,其他的type specifiers都是修饰其他的type specifier。所以下面的几种表达都是一样的:

const unsigned int x;
unsigned int const x;
unsigned const int x;
int const unsigned x;

2.4 Who Got Petrified?

在我们之前学习的过程中,常常会困惑于是指向常量的指针?还是常指针指向一个变量?我们需要注意:const是一个type specifier,而constexpr是一个non-type specifier。

constexpr unsigned long int *x[N]; // 指向变量数组的常指针 
const unsigned long int *x[N]; // 指向常量的数组指针
unsigned long int *const x[N]; // 指向变量数组的常指针

2.5 Const as a Promise

const类型可以转换成const类型的变量,但是反过来并不成立。const相当于一种保证,使用变量的函数或表达式(借),可以使用const保证原先的变量(被借)不被改变。但是反过来,如果变量本来就不允许被改变,但是函数或表达式不提供不被改变的保证,那么就会出错。

int ver;
const int const_ver = 10;

int add1(const int const_ver, const int ver); // ok
int add2(const int const_ver, int ver); // ok
int add3(int const_ver, int ver); // error

add a const is ok, but not to lose it.(同样适用于volatile, const-volatile 被称为CV qulifier)