天天看點

C++關鍵字:mutable、volatile、explicit以及__based

mutable關鍵字

   關鍵字mutable是C++中一個不常用的關鍵字,他隻能用于類的非靜态和非常量資料成員我們知道一個對象的狀态由該對象的非靜态資料成員決定,是以随着資料成員的改變,對像的狀态也會随之發生變化!

如果一個類的成員函數被聲明為const類型,表示該函數不會改變對象的狀态,也就是該函數不會修改類的非靜态資料成員.但是有些時候需要在該類函數中對類的資料成員進行指派.這個時候就需要用到mutable關鍵字了

例如:

C++關鍵字:mutable、volatile、explicit以及__based

1

C++關鍵字:mutable、volatile、explicit以及__based

class Demo

2

C++關鍵字:mutable、volatile、explicit以及__based

{

3

C++關鍵字:mutable、volatile、explicit以及__based

public:

4

C++關鍵字:mutable、volatile、explicit以及__based

   Demo(){}

5

C++關鍵字:mutable、volatile、explicit以及__based

~Demo(){}

6

C++關鍵字:mutable、volatile、explicit以及__based

7

C++關鍵字:mutable、volatile、explicit以及__based

bool getFlag() const

8

C++關鍵字:mutable、volatile、explicit以及__based

9

C++關鍵字:mutable、volatile、explicit以及__based

       m_nAccess++;

10

C++關鍵字:mutable、volatile、explicit以及__based

return m_bFlag;

11

C++關鍵字:mutable、volatile、explicit以及__based

   }

12

C++關鍵字:mutable、volatile、explicit以及__based

private:

13

C++關鍵字:mutable、volatile、explicit以及__based

int  m_nAccess;

14

C++關鍵字:mutable、volatile、explicit以及__based

bool m_bFlag;

15

C++關鍵字:mutable、volatile、explicit以及__based

};

16

C++關鍵字:mutable、volatile、explicit以及__based

17

C++關鍵字:mutable、volatile、explicit以及__based

int main()

18

C++關鍵字:mutable、volatile、explicit以及__based

19

C++關鍵字:mutable、volatile、explicit以及__based

return0;

20

C++關鍵字:mutable、volatile、explicit以及__based

}

21

C++關鍵字:mutable、volatile、explicit以及__based

編譯上面的代碼會出現 error C2166: l-value specifies const object的錯誤說明在const類型的函數中改變了類的非靜态資料成員.這個時候需要使用mutable來修飾一下要在const成員函數中改變的非靜态資料成員

m_nAccess,代碼如下:

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

   mutable int  m_nAccess;

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

這樣再重新編譯的時候就不會出現錯誤了!

volatile關鍵字

volatile是c/c++中一個鮮為人知的關鍵字,該關鍵字告訴編譯器不要持有變量的臨時拷貝,它可以适用于基礎類型

如:int,char,long......也适用于C的結構和C++的類。當對結構或者類對象使用volatile修飾的時候,結構或者類的所有成員都會被視為volatile.使用volatile并不會否定對CRITICAL_SECTION,Mutex,Event等同步對象的需要

例如:

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

int i;

C++關鍵字:mutable、volatile、explicit以及__based

i = i + 3;

無論如何,總是會有一小段時間,i會被放在一個寄存器中,因為算術運算隻能在寄存器中進行。一般來說,volatitle關鍵字适用于行與行之間,而不是放在行内。

我們先來實作一個簡單的函數,來觀察一下由編譯器産生出來的彙編代碼中的不足之處,并觀察volatile關鍵字如何修正這個不足之處。在這個函數體記憶體在一個busy loop(所謂busy loop也叫做busy waits,是一種高度浪費CPU時間的循環方法)

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

void getKey(char* pch)

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

while (*pch ==0);

C++關鍵字:mutable、volatile、explicit以及__based

當你在VC開發環境中将最優化選項都關閉之後,編譯這個程式,将獲得以下結果(彙編代碼)

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

;       while (*pch ==0)

C++關鍵字:mutable、volatile、explicit以及__based

$L27

C++關鍵字:mutable、volatile、explicit以及__based

; Load the address stored in pch

C++關鍵字:mutable、volatile、explicit以及__based

mov eax, DWORD PTR _pch$[ebp]

C++關鍵字:mutable、volatile、explicit以及__based

; Load the character into the EAX register

C++關鍵字:mutable、volatile、explicit以及__based

movsx eax, BYTE PTR [eax]

C++關鍵字:mutable、volatile、explicit以及__based

; Compare the value to zero

C++關鍵字:mutable、volatile、explicit以及__based

test eax, eax

C++關鍵字:mutable、volatile、explicit以及__based

; If not zero, exit loop

C++關鍵字:mutable、volatile、explicit以及__based

jne $L28

C++關鍵字:mutable、volatile、explicit以及__based

;

C++關鍵字:mutable、volatile、explicit以及__based

jmp $L27

C++關鍵字:mutable、volatile、explicit以及__based

$L28

C++關鍵字:mutable、volatile、explicit以及__based

;}

這段沒有優化的代碼不斷的載入适當的位址,載入位址中的内容,測試結果。效率相當的低,但是結果非常準确現在我們再來看看将編譯器的所有最優化選項開關都打開以後,重新編譯程式,生成的彙編代碼,和上面的代碼

比較一下有什麼不同

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

;{

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

mov eax, DWORD PTR _pch$[esp-4]

C++關鍵字:mutable、volatile、explicit以及__based

; Load the character into the AL register

C++關鍵字:mutable、volatile、explicit以及__based

movsx al, BYTE PTR [eax]

C++關鍵字:mutable、volatile、explicit以及__based

; while (*pch ==0)

C++關鍵字:mutable、volatile、explicit以及__based

; Compare the value in the AL register to zero

C++關鍵字:mutable、volatile、explicit以及__based

test al, al

C++關鍵字:mutable、volatile、explicit以及__based

; If still zero, try again

C++關鍵字:mutable、volatile、explicit以及__based

je SHORT $L84

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

從代碼的長度就可以看出來,比沒有優化的情況要短的多。需要注意的是編譯器把MOV指令放到了循環之外。這在單線程中是一個非常好的優化,但是,在多線程應用程式中,如果另一個線程改變了變量的值,則循環永遠不會結束。被測試的值永遠被放在寄存器中,是以該段代碼在多線程的情況下,存在一個巨大的BUG。解決方法是重新

寫一次getKey函數,并把參數pch聲明為volatile,代碼如下:

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

void getKey(volatilechar* pch)

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

while (*pch ==0) ;

C++關鍵字:mutable、volatile、explicit以及__based

這次的修改對于非最優化的版本沒有任何影響,下面請看最優化後的結果:

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

;{

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

$L84:

C++關鍵字:mutable、volatile、explicit以及__based

; Directly compare the value to zero

C++關鍵字:mutable、volatile、explicit以及__based

cmp BYTE PTR [eax], 0

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

這次的修改結果比較完美,位址不會改變,是以位址聲明被移動到循環之外。位址内容是volatile,是以每次循環之中它不斷的被重新檢查。把一個const volatile變量作為參數傳遞給函數是合法的。如此的聲明意味着函數不能改變變量的值,但是變量的值卻可以被另一個線程在任何時間改變掉。

explicit關鍵字

我們在編寫應用程式的時候explicit關鍵字基本上是很少使用,它的作用是"禁止單參數構造函數"被用于自動型别轉換,其中比較典型的例子就是容器類型,在這種類型的構造函數中你可以将初始長度作為參數傳遞給構造函數.

你可以聲明這樣一個構造函數

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

class Array

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

explicit Array(int size);

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

在這裡explicit關鍵字起着至關重要的作用,如果沒有這個關鍵字的話,這個構造函數有能力将int轉換成Array.一旦這種情況發生,你可以給Array支派一個整數值而不會引起任何的問題,比如:

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

Array arr;

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

arr =40;

此時,C++的自動型别轉換會把40轉換成擁有40個元素的Array,并且指派給arr變量,這個結果根本就不是我們想要的結果.如果我們将構造函數聲明為explicit,上面的指派操作就會導緻編譯器報錯,使我們可以及時發現錯誤.需要注意的是:explicit同樣也能阻止"以指派文法進行帶有轉型操作的初始化";

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

Array arr(40);//正确

C++關鍵字:mutable、volatile、explicit以及__based

Array arr =40;//錯誤

看一下以下兩種操作:

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

X x;

C++關鍵字:mutable、volatile、explicit以及__based

Y y(x);//顯式類型轉換

另一種

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

Y y = x;//隐式類型轉換

這兩種操作存在一個小小的差别,第一種方式式通過顯式類型轉換,根據型别x産生了型别Y的新對象;第二種方式通過隐式轉換産生了一個型别Y的新對象.explicit關鍵字的應用主要就是上面所說的構造函數定義種,參考該關鍵字的應用可以看看STL源代碼,其中大量使用了該關鍵字

   __based關鍵字

該關鍵字主要用來解決一些和共享記憶體有關的問題,它允許指針被定義為從某一點開始算的32位偏移值,而不是記憶體種的絕對位置

舉個例子:

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

typedef struct tagDEMOSTRUCT {

C++關鍵字:mutable、volatile、explicit以及__based

int a;

C++關鍵字:mutable、volatile、explicit以及__based

char sz[10];

C++關鍵字:mutable、volatile、explicit以及__based

} DEMOSTRUCT, * PDEMOSTRUCT;

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

HANDLE hFileMapping = CreateFileMapping(

C++關鍵字:mutable、volatile、explicit以及__based

);

C++關鍵字:mutable、volatile、explicit以及__based

LPVOID lpShare = (LPDWORD)MapViewOfFile(

C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based
C++關鍵字:mutable、volatile、explicit以及__based

DEMOSTRUCT __based(lpShare)* lpDemo;

C++關鍵字:mutable、volatile、explicit以及__based

   上面的例子聲明了一個指針lpDemo,内部儲存的是從lpShare開始的偏移值,也就是lpDemo是以lpShare為基準的偏移值.

繼續閱讀