explorer

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

0%

[What] Effective C++ :const,enum,inline 优于 define

宏在 c++ 中要尽可能少的使用,在google c++ 编码规范中也是建议使用宏要谨慎。

宏的弊端

宏无法产生符号

由于宏是在预编译过程中进行替换的,那么在随后生成的目标文件/可执行文件中,就不会包含有该宏的符号。这在某些调试的情况下,不那么方便。

比如下面的示例,先使用宏:

1
2
3
4
5
6
7
8
9
10
#include <iostream>

#define ASPECT_RATION (1.653f)

int main(void){

float val = ASPECT_RATION;

return 0;
}

那么在符号表中无法找到该符号:

1
~/lab/cpp$ readelf -s a.out | grep "ASPECT_RATION"

下面使用 const 变量来替换宏:

1
2
3
4
5
6
7
8
9
10
#include <iostream>

const float kAspectRation = 1.653f;

int main(void){

float val = kAspectRation;

return 0;
}

这样就可以找到符号表:

1
2
~/lab/cpp$ readelf -s a.out | grep "kAspectRation"
37: 0000000000000838 4 OBJECT LOCAL DEFAULT 16 _ZL13kAspectRation

写宏时需要小心翼翼

宏展开后的真实代码和我们以为的代码可能会不同,遇到这种问题还需要去查看预编译后的文件才能清楚。

所以写宏的时候需要小心翼翼,尤其是宏内容较复杂,有多个换行的情况下。这无疑是增加了程序员的负担。

宏没有类内范围性

比如我们只想在类内使用该宏,但其实它的作用范围已经超出了宏:

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 GamePlayer{
public:
int GetArraySize(void) const;
private:
#define NUM_TURNS (5)
int scores_[NUM_TURNS];
};

int GamePlayer::GetArraySize(void) const{
return sizeof(scores_);
}

int main(void){

GamePlayer game_player;

std::cout << "size of array is " << game_player.GetArraySize() << "\n";
std::cout << "macro value " << NUM_TURNS << "\n";

return 0;
}

使用常量替换 #define

全局常量

通常使用常量来替换#define时,为了常量可以被多个文件所使用,需要将其放在头文件内。

对于想要定义常量字符串,有两种做法:

  • 定义指向常量字符串的常量指针:

    1
    const char* const kAuthorName = "Scott Meyers";
  • 定义常量string对象:

    1
    const std::string kAuthorName("Scott Meyers");

定义常量string对象是更加合适的方法,因为不仅由于其不是指针而更好操作外,而且它还具有一系列成员函数,能满足更多的适用场合。

类内的常量

当仅需要在类内定义一个常量时,则需要在类的内部使用static const来修饰它:

static修饰是为了在多个类中,仅占用一个变量的空间。

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 GamePlayer{
public:
int GetArraySize(void) const;
private:
// 如果不取 static 常量的地址,则无需提供定义
static const int num_turns_ = 5;
int scores_[num_turns_];
};

int GamePlayer::GetArraySize(void) const{
return sizeof(scores_);
}

int main(void){

GamePlayer game_player;

std::cout << "size of array is " << game_player.GetArraySize() << "\n";

return 0;
}

还有一种方式是使用枚举来完成常量的定义:

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

class GamePlayer{
public:
int GetArraySize(void) const;
private:
enum{kNumTurns = 5};
int scores_[kNumTurns];
};

int GamePlayer::GetArraySize(void) const{
return sizeof(scores_);
}

int main(void){

GamePlayer game_player;

std::cout << "size of array is " << game_player.GetArraySize() << "\n";

return 0;
}

使用内联函数替代宏函数

宏函数除了写起来很麻烦,也比较容易出现难以排查的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

#define CALL_WITH_MAX(a, b) ((a) > (b) ? (a) : (b))

int main(void){
int a = 10, b = 0;
// 由于宏的直接替换性,a 会被加两次,fuck!
CALL_WITH_MAX(++a, b);

std::cout << "value of a is " << a << "\n";

return 0;
}

而使用inline函数则可以避免这些问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

template<typename T>
inline T CallWithMax(const T& a, const T&b){
return (a > b ? a : b);
}

int main(void){
int a = 10, b = 0;

// 由于调用的是函数,所以这里 a 只会被加一次,完全符合语义
std::cout << "max is " << CallWithMax(++a, b) << "\n";

std::cout << "value of a is " << a << "\n";

return 0;
}