explorer

万丈高楼平地起,勿在浮沙筑高台

0%

Effective C++ :尽可能使用 const

谷歌 c++ 编码规范也比较推崇尽量使用const

const对指针的修饰

const对指针的修饰就看它与星号的位置关系:

  • 如果const在星号左边,则被指对象是常量:const int* p = &val
  • 如果const在星号右边,则指针自身是常量:int* const p = &val
  • 如果const在星号左右都有,则被指对象和指针都是常量:const int* const p = &val

在使用迭代器时,如果不希望改变容器元素的内容,那么就应该使用const_iterator

如果使用const修饰iterator,则得到的是T* const指针,也就是指针自身是常量,但被指对象可以被改变。

当函数要返回一个指针或引用时,需要考虑是否允许用户可以修改此返回值,如果不允许修改,则需要加上const限定。

const成员函数

当成员函数不会修改成员变量时,应该为其加上const限定:

  • 一来可以让接口更为明确
  • 二来可以操作const对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

class Hello {
public:
void Print(void) {
std::cout << "non const!\n";
}
void Print(void) const{
std::cout << "const!\n";
}
};

int main(void) {
Hello hello;

hello.Print();

const Hello chello;
// 假如没有 const 版本的 print,则此行代码无法通过编译
chello.Print();

return 0;
}

需要理解的是const仅限定该成员函数不会改变成员变量,但被该成员变量指向的内存还是有可能被改变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <cstring>
#include <cstdlib>

#include <iostream>

class CTextBlock {
public:
CTextBlock(const char* val) {
int len;
if (!val) {
len = 1;
} else {
len = std::strlen(val) + 1;
}

ptext_ = new char[len];
std::memcpy(ptext_, val, std::strlen(val));
ptext_[len - 1] = '\0';
}

~CTextBlock() {
if (ptext_) {
delete[] ptext_;
}
}

char& operator[](std::size_t position) const {
return ptext_[position];
}

const char* Text(void) const {
return ptext_;
}

private:
char* ptext_ {nullptr};
};

int main(void) {
const CTextBlock cctb("Hello");

std::cout << "The contents of obj are : " << cctb.Text() << "\n";

// 使用
char* pc = &cctb[0];
// ptext 指向的内存被改变了
*pc = 'J';

std::cout << "The contents of obj after modified are : " << cctb.Text() << "\n";

return 0;
}

当一个类需要实现constnon-const两个版本函数时,为了避免代码重复,应该使non-const版本调用const版本。

non-const添加const限定,使用static_cast即可,这是安全的。

而要去除const显示,使用const_cast,使用前需要谨慎。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

class Hello {
public:
void print(void) const {
std::cout << "const hello world!\n";
}
void print(void) {
std::cout << "cast to const\n";
// 将对象 cast 为 const 型,然后就会调用到 const 限定的 print
static_cast<const Hello>(*this).print();
}
};

int main(void) {
const Hello chello;

chello.print();

Hello hello;

hello.print();
return 0;
}

const 成员函数与线程安全

const成员函数在正常情况下,不能对该对象的私有成员变量进行更改,那该函数执行的就是只读操作。

那么多线程调用该成员函数是可以的,例外的是对mutable修饰的变量进行更改。

为了避免const成员函数的mutalbe变量在多线程情况下的数据竞争,需要使用互斥锁、原子锁等方式保证临界区的互斥。

当有多个原子锁且有一定顺序时,应该使用互斥锁将它们做成一个整体。