EffectiveCPP 阅读笔记

Item 2: 用 consts, enums 和 inlines 取代 #defines

使用define的缺陷:

  • 1 预处理器盲目多次拷贝替换宏名,导致产生更多的代码 -> 使用const常量解决
  • 2 宏函数的嵌套需要打上括号 -> 使用内连函数达到相同的性能解决

使用enum替换const的用例:

1
2
3
4
5
6
7
8
class GamePlayer {
private:
enum { NumTurns = 5 }; // "the enum hack" - makes
// NumTurns a symbolic name for
5
int scores[NumTurns]; // fine
...
};

优点有其二:

  • 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
    12
    char 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 f2
  • 2 用于迭代器:
    声明一个iterator 为 const 就类似于声明一个 pointer(指针)为 const(也就是说,声明一个 T* const pointer(指针)):不能将这个 iterator 指向另外一件不同的东西,但是它所指向的东西本身可以变化。
    若需要iterator指向的东西不能变,使用const_iterator即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    std::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 cIter

    2 用于函数

  • 1 返回值和传参是const
    避免了=和==的错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class 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 的方式传递一个对象)。

具体看如下代码:

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
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[]
for
{ return text[position]; } // const objects

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

private:
std::string text;
};
// ------------------- 1 -----------------------
TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const
// TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const
TextBlock::operator[]

// -------------------- 2 ----------------------
void print(const TextBlock& ctb) // in this function, ctb is
const
{
std::cout << ctb[0]; // calls const TextBlock::operator[]
...
}
// --------------------- 3 ---------------------
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

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CTextBlock {
public:
...
char& operator[](std::size_t position) const // inappropriate
(but bitwise
{ return pText[position]; } // const)
declaration of
// operator[]
private:
char *pText;
};
// ----------------------------------------------
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"

上述过程中:你用一个 particular value(确定值)创建一个
constant object(常量对象),然后你只是用它调用了 const member
functions(成员函数),但是你还是改变了它的值!
这就引出了 logical constness(逻辑常量性)的概念。这一理论的信
徒认为:一个 const member function(成员函数)可能会改变调用它
的 object(对象)中的一些 bits(二进制位),但是只能用客户无法
察觉的方法。例如,你的 CTextBlock class(类)在需要的时候可以
储存文本块的长度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
// 可以解脱的代码
// mutable std::size_t textLength; // these data members may
// mutable bool lengthIsValid; // always be modified, even
in
std::size_t textLength; // last calculated length of
textblock
bool lengthIsValid; // whether length is currently
valid
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // error! can't assign to textLength
lengthIsValid = true; // and lengthIsValid in a const
} // member function
return textLength;
}

上述代码会由于成员函数length()后面跟了一个const使得编译器保证了类的二进制常量性,那么想要修复这个问题,则只用mutable将其从中解脱。

4 const和non-const函数有相同实现的单向调用

为避免代码重复,则non版本调用const版本,具体例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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&>( // cast away const on
// op[]'s return type;
static_cast<const TextBlock&>(*this)[position] // add const to *this's type; // call const version of op[]
); }
...
};

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(代码重复)。