天天看點

Effective C++,rule 2,Prefer const,enum and inlines to #define前言#define 常量與const 常量的對比enum hack精華

前言

rule2主要講的是#define相關的一些東西,由标題可知,這裡說的是#define的在某些方面的不足以及一些可行的建議。另外,本節一個重要的内容就是enum hack技術。enum hack 既有 const int(char) “變量”的特性(可被通路控制),又有#define的特征(無法取其位址,從很大程度上杜絕妄圖非法修改常量的行為)

#define 常量與const 常量的對比

先看兩段代碼

#include <stdio.h>
#define PI (3.1415926)
const double pi = ;
int main()
{
    double a = PI,b;
    a = PI*PI;
    b = pi;
    return ;
}
           

彙編:

_main:
  push        ebp  
  mov         ebp,esp  
  sub         esp,h  
  push        ebx  
A  push        esi  
B  push        edi  
C  lea         edi,[ebp-h]  
  mov         ecx,h  
  mov         eax,CCCCCCCCh  
C  rep stos    dword ptr es:[edi]  
E  movsd       xmm0,mmword ptr ds:[h]  
A6  movsd       mmword ptr [a],xmm0  
AB  movsd       xmm0,mmword ptr ds:[h]  
B3  movsd       mmword ptr [a],xmm0  
B8  movsd       xmm0,mmword ptr ds:[h]  
C0  movsd       mmword ptr [b],xmm0  
C5  xor         eax,eax  
C7  pop         edi  
C8  pop         esi  
C9  pop         ebx  
CA  mov         esp,ebp  
CC  pop         ebp  
CD  ret
           

這兩種方式都是定義PI的很常見的方式,說不上誰一定更好或更壞。但是,#define這種方法的原理是在所有 下文中出現 PI的地方都把字元“PI”替換成”3.1415926”了,這個工作是在預編譯的階段完成的。這種的缺點是:

1. 所有PI都會換成了3.1415926,那麼這會導緻預編譯後的代碼長度變長(比如說,一個程式有100個地方使用了PI,那麼這個100個地方的PI全部換成了(3.1415926)這樣長長的一串。然而 這一點無傷大雅。因為 在進階一些的編譯器中實際的彙編代碼中,3.1415926這個常量是會被編譯器放在統一的常量區的。如上面的PI的值(3.1415926) 就放在ds:[1225868h]内,pi的值3.1415926放在ds:[1225858h]内,在彙編語言中都是間接尋址,而不是直接把這個長長的常量寫在mov指令裡面。

2. 調試的過程中,如果與PI相關的地方出了錯誤,那麼報錯資訊就不會”XXX在PI 有錯誤”,而是寫成”xxx在3.1415926有錯誤”,顯然後面直接給個數字的就不好尋找問題源了,特别是跨檔案的時候···。但是如果就使用pi則可以避免這個的問題,如果出錯,就會報:“XXX在pi有錯誤”類似的錯誤,這樣友善調試。

另外,由define定義的常量沒有作用域與通路控制一說的。如下代碼所示:

#include <stdio.h>

class Super
{
private:
    #define PI ()
public:
    const double pi = ;
};
int main()
{
    Super s;
    double a = PI,b;
    a = PI*PI;
    b = s.pi;
    double *p1 = &(PI);
    double *p2 = (double *)&(s.pi);
    *p2 = ;
    return ;
}
           

在類外依舊可以使用PI這個符号。同時在 doule *p1=&(PI)是會報錯的,它妄圖取PI這個符号的位址。

其錯誤為:

那有沒有辦法,既讓常量得到封裝又讓常量無法被取位址的呢?

也就是 說 讓 double * p2=(double*) &(s.pi)錯誤,否則正如上面最後兩句代碼所示, 這個常量其實已經被修改了。這就需要使用到enum hack 技術

enum hack

直接看代碼吧!

#include <stdio.h>
class Super
{

public:
    enum MyEnum
    {
        pi = 
    };
};
int main()
{
    Super s;
    double b;
    b = s.pi;
    double *p2 = (double *)&(s.pi);
    *p2 = ;
    return ;
}
           

在編譯的時候,在double p2 = (double )&(s.pi);會報:

的錯誤。

這是因為 enum裡面枚舉值首先是const int,同時enum結構裡面的各個

const int是無法取得位址。

這樣就達到了既可以封裝,又防止得到這個常量被取位址的目的。

但是enum hack的局限在于 ,隻支援const int.

精華

  1. 若要在類外使用常量,推薦使用const變量。
  2. 若要在類内使用帶封裝的常量,那麼肯定不能用#define,隻能使用const或者enum.
  3. enum hack隻支援整形常量,但是它即提供封裝性又提供不能取其位址的雙重特性。

繼續閱讀