「生活可以更简单, 欢迎来到我的开源世界」
  1. const 与指针
    1. 变与不变
  2. const 与迭代器
  3. const 与函数
    1. 函数返回 const value
    2. const 成员函数
    3. bitwise constness 和 logical constness
    4. 在 const 和 non-const 成员函数中避免重复
  4. 总结
Item3-尽可能使用const
2021-01-01
」 「

Item 3: 尽可能使用 const

const 指定一个“不该被改动”的对象,编译器会强制实施这项约束。

const 与指针

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

C++ Primer 5th :弄清楚声明的含义:从右向左阅读,离变量名最近的对变量有直接影响,其余部分确定对象的类型

当指针指向的内容是常量时,将 const 放在类型前和放在类型后是没有区别的:

//@ 等价的形式
void f1(const Widget *pw);
void f1(Widget const *pw);

变与不变

当指针指向的内容是常量时,表示无法通过指针修改变量的值,但是可以通过其它方式修改指针指向变量的值:

int a = 1;
const int *p = &a;
cout << *p << endl; //@ 1
*p = 2; //@ error, data is const
a = 2;
cout << *p << endl; //@ 2

指针本身是常量,表示指针表示的地址是固定的,但是其指向的内容是可以改变的:

int a = 1, b = 2;
int* const p = &a;
cout << *p << endl; //@ 1
p = &b; //@ error, pointer is const
*p = b;
cout << *p << endl; //@ 2

const 与迭代器

STL 迭代器以指针为原型,所以 iterator 的作用就像个 T* 指针。声明一个 iterator 为 const 就类似于声明一个 pointer 为 const(也就是说,声明一个 T* const pointer):

std::vector<int> vec;

const std::vector<int>::iterator iter = vec.begin();
*iter = 10; //@ ok,change what the iterator point to
iter++; //@ error,iter is const

std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10; //@ error,*cIter is const
++cIter; //@ ok,change cIter

const 与函数

const 可以用在函数返回值,函数的个别参数,对于成员函数,还可以用于整个函数。

函数返回 const value

令函数返回一个常量值,往往可以在不放弃安全性和高效性的前提下降低因客户错误而造成的意外。

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

Rational a,b,c;
...
(a * b) = c; //为两个数的乘积赋值,将返回值声明为const 可以避免此问题

const 成员函数

class TextBlock {
public:
...
//@ operator[] for const objects
const char& operator[](std::size_t position) const
{ return text[position]; }

//@ operator[] for non-const objects
char& operator[](std::size_t position)
{ return text[position]; }

private:
std::string text;
};

//@ 使用
TextBlock tb("Hello");
std::cout << tb[0]; //@ calls non-const TextBlock::operator[]

const TextBlock ctb("World");
std::cout << ctb[0]; //@ calls const TextBlock::operator[]

真实程序中 const 对象大多用于 passed by pointer-to-const passed by reference-to-const

void print(const TextBlock& ctb)       // in this function, ctb is const
{
std::cout << ctb[0]; // calls const TextBlock::operator[]
...
}
//@ 对 const 和 non-const 的 TextBlocks 做不同的操作
std::cout << tb[0]; //@ fine — reading a non-const TextBlock
tb[0] = 'x'; //@ fine — writing a non-const TextBlock
std::cout << ctb[0]; //@ fine — reading a const TextBlock
ctb[0] = 'x'; //@ error! — writing a const TextBlock

错误只与 operator[] 返回类型有关,而 operator[] 调用动作自身没问题。

如果 non-const 版本的 operator[] 返回一个char而不是一个char引用,则下面语句将无法编译通过:

tb[0] = 'x';	//返回一个右值,企图为一个右值赋值

bitwise constness 和 logical constness

(比特常量和逻辑常量)

比特常量:成员函数只有在不更改对象内的任何非静态成员变量,那该函数是const的,即不更改对象内任何一个 bit。比特常量是C++ 对常量性(constness)的定义。

不幸的是,许多成员函数虽然不具备 const 性质,却能通过 bitwise测试:一个更改了“指针所指物”的成员函数不能算const,但如果只有指针(而非其所指物)隶属于对象,则称此函数为 bitwise const 不会引发编译器异议。

class CTextBlock {
public:
...
char& operator[](std::size_t position) const // inappropriate (but bitwise
{ return pText[position]; } // const) declaration of
// operator[]
private:
char *pText;
};

看看 operator[] 的实现,它并没有使用任何手段改变 pText。结果,编译器愉快地生成了 operator[] 的代码,因为毕竟对所有编译器而言,它都是 bitwise const 的,但是我们看看会发生什么:

const CTextBlock cctb("Hello");   //@ declare constant object
char *pc = &cctb[0]; //@ call the const operator[] to get a pointer to cctb's data
*pc = 'J'; //@ cctb now has the value "Jello"

这里确实出了问题,你创建一个常量对象并设以某值,然后你只是用它调用了 const 成员函数,但是你还是改变了它的值!

这种情况导出所谓的 logical constness(逻辑常量):一个 const 成员函数可以修改它所处理的对象内的某些 bits,但只有在客户端侦测不到的情况下才得如此。

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; //即使在const 成员函数内
};

std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); //now fine
lengthIsValid = true; //also fine
}

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;
};

代码重复伴随着编译时间、维护、代码膨胀等问题。

真正需要的是一次 operator[] 功能实现,然后使用它两次,即必须令其中一个调用另一个。

运用 const 成员函数实现了其 non-const 成员函数:

class TextBlock {
public:
...
const char& operator[](std::size_t position) const // same as before
{
...
return text[position];
}

char& operator[](std::size_t position) // now just calls const op[]
{
return const_cast<char&>(
static_cast<const TextBlock&>(*this)[position]);
}
...
};

反之,运用 non-const 成员函数实现了其 const 成员函数是错误的,const成员函数承诺绝不改变其对象的逻辑状态,而 non-const 成员函数没有这般承诺。

non-const成员函数本身就可以对对象做任何动作,因此以static_cast处理*this并不存在风险。

总结

<⇧>