Item 2: Prefer consts, enums, and inlines to #defines

尽量使用常量、枚举和内联函数,代替#define。我们知道#define定义的宏会在编译时进行替换,属于模块化程序设计的概念。 宏是全局的,面向对象程序设计中破坏了封装。因此在C++中尽量避免它!

接着我们具体来看#define造成的问题。

不易理解

众所周知,由于预处理器会直接替换的原因,宏定义最好用括号括起来。#define函数将会产生出乎意料的结果:

#define MAX(a,b) a>b?a:b
MAX(i++,j)

i自加次数将取决于j的大小,然而调用者并不知情。宏的行为不易理解,本质上是因为宏并非C++语言的一部分,它只是源代码的预处理手段。

不利于调试

宏替换发生在编译时,语法检查之前。因此相关的编译错误中不会出现宏名称,我们不知道是哪个宏出了问题。例如:

#define PERSON alice
PERSON = bob;

如果alice未定义,PERSON=bob;便会出错:use of undeclared identifier 'alice'。 然而我们可能不知道alice是什么东西,PERSON才是我们定义的“变量”。

宏替换是在预处理过程中进行的,原则上讲编译器不知道宏的概念。然而,在现代的编译器中(例如Apple LLVM version 6.0), 编译器会记录宏替换信息,在编译错误中给出宏的名称:

test.cpp:8:5: error: use of undeclared identifier 'alice'
    PERSON  = bob;
    ^
test.cpp:4:16: note: expanded from macro 'PERSON'
#define PERSON alice;
               ^

于是,Meyers提到的这个问题已经不存在了。然而作者的本意在于:尽量使用编译器,而不是预处理器。 因为#define并不是C++语言的一部分。

enum 比 const 更好用

既然#define不能封装在一个类中,我们可以用static const来定义一个常量,并把它的作用于局限在当前类:

class C{
    static const int NUM = 3;
    int a[NUM];
};

通常C++要求所有的声明都给出定义,然而数值类型(char, int, long)的静态常量可以只给声明。这里的NUM就是一个例子。 然而,如果你想取NUM的地址,则会得到编译错误:

Undefined symbols for architecture x86_64:
  "C::NUM", referenced from:
      _main in a-88bbac.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

因此如果你要取地址,那么就给出它的定义:

class C{
    static const int NUM = 3;
    int a[NUM];
};
const int C::NUM;

因为声明NUM时已经给定了初始值,定义时不允许再次给初始值。

如果使用enum,事情会简单很多:

class C{
    enum { NUM = 3 };
    int a[NUM];
};

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2015/07/20/effective-cpp-2.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。