欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-15 12:23:16
...

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

“宁可以编译器替换预处理器”,因为#define不被视为语言的一部分。这也是问题所在:

        #define ASPECT_TATIO 1.653

        记号名称ASPECT_TATIO也许从未被编译器看见:也许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称ASPECT_TATIO有可能没进入记号表(symbol table)内。如果由于运用此常量而获得一个编译错误,错误信息可能会提到1.635

而不是ASPECT_RATIO,追踪它将会浪费时间。

    解决之道是以一个常量替换上述的宏(#define):

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

    此外对于浮点常量(folating point const)而言,使用常量可能比使用#define导致较小量的码,因为它解决了预处理器盲目的将ASPECT_RATIO替换为1.635从而导致目标码出现多份1.635的情况.

    有两点特殊情况值得注意:

    1.对于常量指针,由于常量定义式通常放头文件内(便于被不同源码函如),因此有必要将指针(不止是所指之物)声明为const。

        例如:

        const char* const authorName = "Scott Meyers";//定义这个常量的char*-based字符串,你必须写const两次

        另值得提醒:string对象通常比char*based合宜,所以authorName往往这样定义好些:

        const std::string authorName("Scott Meyers");//为防止重复引用在头文件中string都加上std::而不使用using namespace,哪怕很繁琐

 

    2.如果将常量的作用域限制在class内,需要让它成为class的一个成员;而为确保此常量至多有一份实体,需将它设为static成员:

 

    class GamePlayer{
    private:
        static const int NumTurns=5;//常量声明式
        int scores[NumTurns];       //使用该常量
        ...
    };     

    此NumTurns时声明式而非定义式。对于静态成员,C++标准规定必须在类定义体之外定义它们的定义式,然而如果它是整型变量,只要不取它们的地址,则可以只声明它们而无需定义式如果编译器坚持要看到一个定义式,则必须在类外提供定义式如下:
    
const int GamePlayer::NumTurns;//(由于const静态常量在声明时已获初值,定义时不可再设初值).

     然而,由于旧式编译器有可能不允许const静态成员在声明时设初值以及所谓的"in-class 初值设定"只允许对整数常量进行,那么需要将初值放在定义式:

 

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

    由此有可能引发另一问题,就是例如上述GamePlayer::scores的数组声明中,编译器坚持要在编译期间知道数组的大小。编译器(错误的)不允许static整数型class常量完成“in class初值设定”,可改用“the enum hack”补偿做法,其理论基础是:"一个属于枚举类型(enumerated type)的数值可权充ints被使用",于是GamePlayer可定义如下:

 

    class GamePlayer{
    privete:
        enum{NumTurns=5};//the enum hack 令NumTurns成为5的一个记号名称
        int scores[NumTurns];
        ...
    };

    基于数个理由enum hack值得我们认识:
    a. enum hack的行为比较像#define而不像const,enums和#defines一样不会导致内存分配(有时候编译器可能会为const对象分配空间),因而取一个enum和#define的地址都不合法.

    b. 实用主义,许多代码用了它,enum是模板元编程(template metaprogramming)的基础技术.

3.带参宏运算问题

    对于宏尽管它有看起来像函数调用但没有函数调用的额外内存开销,但它属于单纯的文本替换,过度使用可能招致问题:

    //a,b较大值调用f

    #define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))//为宏中的所有实参加上小括号

    使用的时候招致意想不到的事情:

    int a = 5,b = 0;

    CALL_WITH_MAX(++a,b);        //a被累加二次

    CALL_WITH_MAX(++a,++b);    //a被累加一次

  调用f之前,a的递增次数经验取决于 拿他和谁比较!,对于以上这种无聊的问题可使用泛型来解决,即可以获得宏带来的效率以及一般函数的可预料性行为和类型安全性(type safety)——template inline函数

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

template inline 函数同时具备宏带来的效率以及一般函数行为的可预料性和类型安全性,此外,由于callWithMax是个真正的函数,它遵循作用域和访问规则,例如可以写一个class内的private inline函数,而宏定义没有此功能.

 

总结

    对于单纯常量最好以const对象或enums替换#defines;

    对于带参数的宏(macros),最好改用inline函数替换#defines;

需要注意的是,尽管宏定义的需求降低了,在条件编译以及调试输出宏等方面它仍然是不可替代的.

 

 

 

 

 

 

 

 

 

相关标签: Effective C