EffectiveCPP 阅读笔记
Item 2: 用 consts, enums 和 inlines 取代 #defines
使用define的缺陷:
- 1 预处理器盲目多次拷贝替换宏名,导致产生更多的代码 -> 使用const常量解决
- 2 宏函数的嵌套需要打上括号 -> 使用内连函数达到相同的性能解决
使用enum替换const的用例:
1 | class GamePlayer { |
优点有其二:
- 1 避免常量的指针和引用被取走
首先,the enum hack
的行为在几个方面上更像一个 #define 而不是 const,而有时这正是你
所需要的。例如:可以合法地取得一个 const 的 address(地址),
但不能合法地取得一个 enum 的 address(地址),这正像同样不能
合法地取得一个 #define 的 address(地址)。如果你不希望人们得
到你的 integral constants(整型族常量)的 pointer(指针)或
reference(引用),enum(枚举)就是强制约束这一点的好方法。 - 2 被模板元编程大量使用,属于实用主义
总结:
a. 对于 simple constants(简单常量),用 const objects(const 对
象)或 enums(枚举)取代 #defines。
b. 对于 function-like macros(类似函数的宏),用 inline
functions(内联函数)取代 #defines
Item 3: 只要可能就用 const
1 const用于指针,迭代器:
1 用于指针:
如果 const 出现在星号左边,则指针 pointed to(指向)的内容为 constant(常量);
如果 const 出现在星号右边,则 pointer itself(指针自身)为
constant(常量);如果 const 出现在星号两边,则两者都为
constant(常量)。1
2
3
4
5
6
7
8
9
10
11
12char greeting[] = "Hello";
char *p = greeting; // non-const pointer,
// non-const data
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
void f1(const Widget *pw); // f1 takes a pointer to a
// constant Widget object
void f2(Widget const *pw); // so does f22 用于迭代器:
声明一个iterator 为 const 就类似于声明一个 pointer(指针)为 const(也就是说,声明一个 T* const pointer(指针)):不能将这个 iterator 指向另外一件不同的东西,但是它所指向的东西本身可以变化。
若需要iterator指向的东西不能变,使用const_iterator即可1
2
3
4
5
6
7
8
9
10
11
12
13std::vector<int> vec;
...
const std::vector<int>::iterator iter = // iter acts like a T*
const
vec.begin();
*iter = 10; // OK, changes what iter
points to
++iter; // error! iter is const
std::vector<int>::const_iterator cIter = // cIter acts like a
const T*
vec.begin();
*cIter = 10; // error! *cIter is const
++cIter; // fine, changes cIter2 用于函数
1 返回值和传参是const
避免了=和==的错误1
2
3
4
5
6
7
8
9class Rational { ... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
Rational a, b, c;
...
(a * b) = c; // invoke operator= on the
// result of a*b!
if (a * b = c) ... // oops, meant to do a comparison!参数在不会被改变的时候就应该传入const
2 const member functions(const 成员函数)
有两个原因:- 2.1 它使一个 class(类)的 interface(接口)更容易被理
解。知道哪个函数可以改变 object(对象)而哪个不可以是很重要
的。 - 2.2 它们可以和 const objects(对象)一起工作。因为,书写
高效代码有一个很重要的方面,就像 Item 20 所解释的,提升一个
C++ 程序的性能的基本方法就是 pass objects by reference-to const(以传引用给 const 的方式传递一个对象)。
- 2.1 它使一个 class(类)的 interface(接口)更容易被理
具体看如下代码:
1 | class TextBlock { |
3 const的二进制常量性和逻辑常量性
bitwise(二进制位)const 派别坚持认为,一个 member function(成
员函数),当且仅当它不改变 object(对象)的任何 data
members(数据成员)(static(静态的)除外),也就是说如果不改
变 object(对象)内的任何 bits(二进制位),则这个 member
function(成员函数)就是 const。bitwise constness(二进制位常量
性)的一个好处是比较容易监测违例:编译器只需要寻找对 data
members(数据成员)的 assignments(赋值)。实际上,bitwise
constness(二进制位常量性)就是 C++ 对 constness(常量性)的
定义,一个 const member function(成员函数)不被允许改变调用它
的 object(对象)的任何 non-static data members(非静态数据成
员)。
1 | class CTextBlock { |
上述过程中:你用一个 particular value(确定值)创建一个
constant object(常量对象),然后你只是用它调用了 const member
functions(成员函数),但是你还是改变了它的值!
这就引出了 logical constness(逻辑常量性)的概念。这一理论的信
徒认为:一个 const member function(成员函数)可能会改变调用它
的 object(对象)中的一些 bits(二进制位),但是只能用客户无法
察觉的方法。例如,你的 CTextBlock class(类)在需要的时候可以
储存文本块的长度:
1 | class CTextBlock { |
上述代码会由于成员函数length()后面跟了一个const使得编译器保证了类的二进制常量性,那么想要修复这个问题,则只用mutable将其从中解脱。
4 const和non-const函数有相同实现的单向调用
为避免代码重复,则non版本调用const版本,具体例子如下:
1 | class TextBlock { |
cosnt_cast去掉const属性,然后用static_cast将this转为const去调用对应的const函数。
而不能用const成员函数去调用非const版本的是因为需要将cosnt的this转为non-const的this,会导致值发生改变,这就导致const成员函数失去本身意义。
总结:
将某些东西声明为 const 有助于编译器发现使用错误。const 能
被用于任何 scope(范围)中的 object(对象),用于 function
parameters(函数参数)和 return types(返回类型),用于整
个 member functions(成员函数)。
编译器坚持 bitwise constness(二进制位常量性),但是你应该
用 conceptual constness(概念上的常量性)来编程。
当 const 和 non-const member functions(成员函数)具有本质
上相同的实现的时候,使用 non-const 版本调用 const 版本可以
避免 code duplication(代码重复)。