Effective C++

Chapter 1. 让自己习惯C++(Accustoming Yourself to C++)

Item 2. 尽量以 const, enum, inline 替换 #define

(prefer consts, enums and inlines to #define)

这个条款或许可以改为“宁可以编译器替换预处理器”比较好,因为或许 #define 不被视为语言的一部分。

对于

#define ASPECT_RATIO 1.653

记号名称 ASPECT_RATIO 也许从未被编译器看见, 也许在编译器开始处理源码之前它就被预处理器移走了,解决之道是以一个常量替代上述的宏(#define):

const double AspectRatio = 1.653;             //大写名称常用于宏,因此这里改变名称写法

作为一个语言常量,AspectRatio 一定会被编译器看到,而且对于浮点常量(float point constant)而言,使用常量可能比使用 #define 导致较小量的码,因为预处理器“盲目地将宏名称 ASPECT_RATIO 替换为1.653”可能导致目标码出现多份1.653,若改为常量 AspectRatio 绝不会出现相同的情况。

当以常量替换 #define, 有两种情况值得注意:

  • 第一是定义常量指针(constant pointers)。由于常量定义式通常被放在头文件内,因此有必要将指针(而不只是指针所指之物)声明为 const。如若要在头文件内定义一个常量的 char*-based 字符串, 你必须写 const 两次:
const char* const authorName = "Scott Meyers";

但一般 string 对象通常比其前辈 char*-based 合宜,所以定义成这样往往更好:

const std::string authorName("Scott Meyers");
  • 第二是 class 专属常量。为了将常量的作用域(scape)限制于 class 内,你必须让它成为 class 的一个成员(member);而为确保此常量至多只有一份实体,你必须让它成为一个 static 成员:
class GamePlayer
{
private:
static const int NumTurns = 5; //常量声明式
int scores[NumTurns]; //使用该常量
...
};

这里看到的是 NumTurns 的声明式而非定义式,通常 C++ 要求你对所使用的任何东西提供一个定义式,但如果它是个 class 专属常量又是 static 且为整数类型(intergral type,如ints,chars,bools),则需特殊处理。只要不取它们的地址,你可以声明并使用它们而无需提供定义式。但如果需要取某个 class 专属常量的地址,或纵使不取地址但编译器却(不正确地)坚持要看到一个定义式,就必须另外提供如下定义式:

const int GamePlayer::NumTurns;                    //NumTurns的定义

将这个式子放进实现文件而非头文件,由于 class 常量在声明时获得初值,因此定义时不可以再设初值。

旧式编译器也许不支持上述语法,它们不允许 static 成员在其声明式上获得初值。此外所谓的“in-class 初值设定”也只允许对整数常量进行。此时你可以将初值放在定义式:

class CostEstimate
{
private:
static const double FudgeFactor; //static class 常量声明,位于头文件内
...
};
const double CostEstimate::FudgeFactor = 1.35; //static class 常量定义,位于实现文件内

当你在 class 编译期间需要一个 class 常量值,例如在上述的 GamePlayer::scores 的数组声明中(编译器坚持必须在编译期间知道数组的大小)。这时万一编译器(错误地)不允许“static 整数型 class 常量”完成“in class 初值设定”,可改用所谓的“ the enum hack”补偿做法。其理论基础是:“一个属于枚举类型(enumerated type)的数值可权充 ints 被使用”,于是 GamePlayer 被定义如下:

class GamePlayer
{
private:
enum { NumTurns = 5}; //“the enum hack” —— 令 NumTurns 成为 5 的一个记号名称
int scores[NumTurns]; //这就没问题了
...
};

对于 enum hack:

  • 第一,enum hack 的行为某方面说比较像 #define 而不像 const 。例如取一个 const 的地址是合法的,但取一个 enum 的地址就不合法,而取一个 #define 的地址通常也不合法。如果你不想让别人获得一个 pointer 或 reference 指向你的某个整数常量,enum 可以帮你实现这个约束。此外虽然优秀的编译器不会为“整数型 const 对象”设定另外的存储空间(除非你创建一个 pointer 或 reference 指向该对象),不够优秀的编译器却可能如此,而这可能是你不想要的。Enums 和 #defines 一样绝不会导致非必要的内存分配。

  • 第二,实用主义。许多代码用了它,所以看到它时必须认识它。事实上“enum hack“是 template metaprogramming (模板元编程)的基础技术。

另一个常见的 #define 误用是以它实现宏(macros)。宏看起来像函数,却不会招致函数调用(function call)带来的额外开销。下面这个宏夹带着宏实参,调用函数 f :

#define CALL_WITH_MAX (a, b) f((a) > (b) ? (a) : (b))       //以 a 和 b 的较大值调用 f

无论何时写出这种宏,必须记住为宏中的所有实参加上小括号,否则在表达式中调用这个宏时可能会遭遇麻烦。但纵使为所有的实参添加小括号,还是会有不可思议的事情发生:

int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a 被累加两次
CALL_WITH_MAX(++a, b+10) // a 被累加一次

在这里,调用 f 之前,a 的递增次数取决于”它被拿来和谁比较“。

幸运的是,只要写出 template inline 函数,便可获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(type safety):

template<typename T>
inline void callWithMax(const T& a, const T& b) //由于不知道 T 是什么,故采用 pass by reference-to-const
{
f(a > b ? a: b);
}

这个 template 产出一整群函数,每个函数都接受两个同型对象,并以其中较大者调用 f 。此外由于 callWithMax 是个真正的函数,它遵守作用域和访问规则,例如你可以写出一个”class 内的 private inline 函数“。一般而言宏无法完成此事。

有了 consts、enums 和 inlines, 我们对预处理器(特别是 #define )的需求降低了,但并非完全消除。 #include 仍然是必须品,而 #ifdef / #ifndef 也继续扮演控制编译的重要角色。目前还不到预处理器全面引退的时候,但应该明确地给出它更长更频繁的假期。

请记住:

  • 对于单纯常量,最后以 const 对象或 enum 替换 #define。

  • 对于形似函数的宏(macros),最好改用 inline 函数替代 #define 。

Effective C++ 之 Item 2:尽量以 const, enum, inline 替换 #define的更多相关文章

  1. Effective C++ -----条款02:尽量以const, enum, inline替换 #define

    class GamePlayer{private: static const int NumTurns = 5; int scores[NumTurns]; ...}; 万一你的编译器(错误地)不允许 ...

  2. Effective C++ 条款02:尽量以const,enum,inline替换 #define

    换一种说法就是宁可以编译器替换预处理器 举例 #define ASPECT_RATIO 1.653 记号ASPECT_RATIO也许从未被编译器看见:也许在编译起开始处理源码前它就被预处理器移走了,于 ...

  3. Effective C++学习笔记 条款02:尽量以const,enum,inline替换 #define

    尽量使用const替换 #define定义常量的原因: #define 不被视为语言的一部分 宏定义的常量,预处理器只是盲目的将宏名称替换为其的常量值,导致目标码中出现多分对应的常量,而const定义 ...

  4. Effective C++阅读笔记_条款2:尽量以const,enum,inline替换#define

    1.#define缺点1 #define NUM 1.2 记号NUM可能没有进入记号表,在调试或者错误信息中,无法知道1.2的含义. 改善:通过const int NUM = 1.2; 2.#dein ...

  5. NO.2: 尽量以const,enum,inline 替换 #define

    1.首先#define 定义不重视作用域(scope),虽然可以#undef控制,但是不美观,还存在多次替换的问题,以及没有任何封装性. 2.const XXX_XX,保证其常量性以及可控的作用域,如 ...

  6. 读书笔记_Effective_C++_条款二:尽量以const, enum, inline替换#define

    其实这个条款分成两部分介绍会比较好,第一部分是用const和enum替换不带参的宏,第二部分是用inline替换带参的宏. 第一部分:用const和enum替换不带参宏 宏定义#define发生在预编 ...

  7. 条款2:尽量以const, enum, inline替换#define

    原因: 1. 追踪困难,由于在编译期已经替换,在记号表中没有. 2. 由于编译期多处替换,可能导致目标代码体积稍大. 3. define没有作用域,如在类中定义一个常量不行. 做法: 可以用const ...

  8. 条款02:尽量以const,enum,inline替换#define

    目录 1. 总结 2. 使用const常量或enum替换宏常量 class外部的常量指针 class专属常量 1. 总结 对于单纯常量,最好以const常量或enum替换#define 对于宏代码段, ...

  9. Effective C++之条款2:尽量以const enum inline替换 #define

    本文的标题也可以改成“用编译器替换预处理器”: const double AspectRatio = 1.653; //最好使用上述代码替换下述代码: #define ASPECT_RATIO 1.6 ...

  10. 条款2:尽量使用const ,enum,inline替换define

    宁可使用编译器而不用预处理器 假设我们使用预处理器: #define ABC 1.56 这标识符ABC也许编译器没看到,也许它在编译器处理源码前就被预处理器移走了,于是“标识符”ABC没有进入标识符列 ...

随机推荐

  1. 字符串、字符、字节以及bit位小结与疑问

    字符串是由一个个字符组成的,每个字符又有一个或多个字节来表示,每个字节又由8个bit位来表示 在C#里 字符串通常由string来声明,字符由char来声明,字节由byte来表示,位由bit来表示,具 ...

  2. 【leetcode】Scramble String

    Scramble String Given a string s1, we may represent it as a binary tree by partitioning it to two no ...

  3. vb.net多线程

    Public Class Form1 Dim myThread As Threading.Thread Dim myThread2 As Threading.Thread Private Sub Bu ...

  4. Log4perl 的使用

    Perl 使用Log4perl 首先下载log4 module : http://search.cpan.org/CPAN/authors/id/M/MS/MSCHILLI/Log-Log4perl- ...

  5. MySQL 通过idb文件恢复Innodb 数据【转】

    昨晚收到一则求助,一个用户的本地数据库的重要数据由于误操作被删除,需要进行紧急恢复,用户的数据库日常并没有进行过任何备份,binlog也没有开启,所以从备份和binlog入手已经成为不可能,咨询了丁奇 ...

  6. Greedy:Cow Acrobats(POJ 3045)

    牛杂技团 题目大意:一群牛想逃跑,他们想通过搭牛梯来通过,现在定义risk(注意可是负的)为当前牛上面的牛的总重量-当前牛的strength,问应该怎么排列才能使risk最小? 说实话这道题我一开始给 ...

  7. codeforces 493B.Vasya and Wrestling 解题报告

    题目链接:http://codeforces.com/problemset/problem/493/B 题目意思:给出 n 个 techniques,每个 technique 的值为 ai. ai & ...

  8. git remote 相关用法

    为了便于管理,Git要求每个远程主机都必须指定一个主机名.git remote  命令就用于管理主机名. 不带选项的时候,git remote命令列出所有远程主机. $ git remote orig ...

  9. tp5中的一些小方法

    // 当使用一个新页面替换当前页面的body后,body刷新了,所选择的select值就不能保存住,解决方法如下: 作业题目<select> <option>--请选择--&l ...

  10. 【python】类中的self

    在python的类中,经常会写self,代表对象自己.如下例: #coding=utf-8 class Foo: def __init__(self, name): self.name = name ...