Effective C++之以const 、enum、iniline替换#define

  • Post author:
  • Post category:其他


本文总结自<<Effective C++, 改善程序设计的55个条款>>中的条款3 :  “尽量以const, enum, inline 替换#define”(宁可以编译器替代预处理器). 同时辅以<<深入理解计算机系统>>中的第五章“优化程序性能”中的一些内容.

一、用const替换#define

#define RAD_2_DEGREE  180.0/M_PI

宏定义之所以不被推荐使用是因为它在预处理阶段被替换了, 也就是说编译器是看不到宏定义的记号, 符号表中中是找不到 ” RAD_2_DEGREE “的, 这就可能会给我们调试程序时带来很多麻烦, 如果是这个宏定义我们写错了个小数点, 代码中如果存在编译错误的话, 只会报错5.73….,根本无从下手查找错误. 把宏定义替换程一个const 常亮就可以很好地解决问题:

const double RAD_2_DEGREE = 180.0/M_PI;

这时RAD_2_DEGREE是记入符号表的, 而且const常量的写法不会像宏那样在每个使用RAD_2_DEGREE的地方都做一边替换, 占用更多的内存.

用enum替换define不再过多赘述, 总的来说enum会达到和#define一样的效果, 但是enum更具有可读性和安全性, enum也可以称之为模板元编程的基础.

二、用inline替换#define

当你接手的项目里有如下几行代码:

#define CALL_WITH_MAX_FUNC(a,b) func((a) > (b) ? (a) : (b))

int a = 5, b = 0;
CALL_WITH_MAX_FUNC(++a, b);
CALL_WITH_MAX_FUNC(++a, b+10);

恭喜你, 遇到了让人崩溃的代码和bug! 光是这一行宏定义就够让人难受了, 后面第一次调用宏CALL_WITH_MAX_FUNC会把a自增两次, 而第二次调用宏CALL_WITH_MAX_FUNC只会自增a一次!a的自增次数取决于更他比较的数的大小。。。

还好我们有更优雅的方式来解决这种无聊问题, inline函数不仅不需要函数调用的开销,还包含类型安全检查、遵守作用域规则. 上述宏定义可以替换为

template <typename T>
inline void call_with_max_func(const T& a, const T& b)
{
    f(a>b ? a : b);
}

inline函数另一个可贵的地方,是它可以为我们的程序优化提供极大的便利. 虽然现在gcc已经发展到了9.2版本, 代码优化功能已经非常智能了, 但是作为程序员还是要了解对于编译器哪些可为、哪些不可为. 我们将gcc优化指令调到最低 -O1, 方便我们窥探不同的程序设计对编译器代码优化的影响.

考虑下面两个过程:

long f();

long func1()
{
    return f() + f() + f() + f();
}

long func2()
{
    return 4*f();
}

乍一看两个函数应该会得到相同的结果, 为了简约我们会选择第二种写法, 但是如果f()的实现是这样的话:

// global variable
long count = 0;

long f()
{
    return count++;
}

每次调用f()都会需改一个全局变量, 那么 func1()和 func2()的结果就完全不一样了!  我们称这样的f()为”有副作用的函数”, 我们想依赖编译器来优化函数func1(), 但是大多数的编译器不会去判断一个函数中是否有副作用,它会假设最糟糕的情况, 即func1() 中是存在副作用的, 并保持原有代码不变.

一种方法是我们使用内联函数来替换f()的四次调用, 展开func1()的代码:

inline void f()
{
    return count++;
}

// result of inlining f in func1
long func1in()
{
    long t = count++;
    t += count++;
    t += count++;
    t += count++;
    return t;
}

这样就允许编译器对func1()做进一步的优化了, 产生一个优化版本的代码:

// optimized inline code
long func1opt()
{
    long t = 4* count +6;
    count += 4;
    return t;
}

对与函数f()的定义, 上述优化代码不仅减少了函数调用的开销, 并且忠实的复现了func1的行为.

gcc大部分版本都会进行这种形式的优化, 或者在命令行选项中显示著名 “-finline “或者” -O1 “及更高级别优化等级.

不过inline函数也有缺点, 那就是无法使用调试器来进行追踪, 这在我们剖析程序性能时是个弊端。



版权声明:本文为fang_chuan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。