C++中修飾數據可變的關鍵字有三個:const、volatile和mutable。const比較好理解,表示其修飾的內容不可改變(至少編譯期不可改變),而volatile和mutable恰好相反,指示數據總是可變的。mutable和volatile均可以和const搭配使用,但兩者在使用上有比較大差別。
一、mutable關鍵字
mutable的中文意思是“可變的,易變的”,跟constant(既C++中的const)是反義詞。
在C++中,mutable也是為了突破const的限制而設置的。被mutable修飾的變量,將永遠處於可變的狀態,即使在一個const函數中。甚至結構體變量或者類對象為const,其mutable成員也可以被修改。mutable在類中只能夠修飾非靜態數據成員。
我們知道,如果類的成員函數不會改變對象的狀態,那么這個成員函數一般會聲明成const的。但是,有些時候,我們需要在const的函數里面修改一些跟類狀態無關的數據成員,那么這個數據成員就應該被mutalbe來修飾。下面是一個小例子:
1 class ClxTest 2 { 3 public: 4 void Output() const; 5 }; 6 7 void ClxTest::Output() const 8 { 9 cout << "Output for test!" << endl; 10 } 11 12 void OutputTest(const ClxTest& lx) 13 { 14 lx.Output(); 15 }
類ClxTest的成員函數Output是用來輸出的,不會修改類的狀態,所以被聲明為const的。函數OutputTest也是用來輸出的,里面調用了對象lx的Output輸出方法,為了防止在函數中調用其他成員函數修改任何成員變量,所以參數也被const修飾。
如果現在,我們要增添一個功能:計算每個對象的輸出次數。如果用來計數的變量是普通的變量的話,那么在const成員函數Output里面是不能修改該變量的值的;而該變量跟對象的狀態無關,所以應該為了修改該變量而去掉Output的const屬性。這個時候,就該我們的mutable出場了——只要用mutalbe來修飾這個變量,所有問題就迎刃而解了。下面是修改過的代碼:
1 class ClxTest 2 { 3 public: 4 ClxTest(); 5 ~ClxTest(); 6 7 void Output() const; 8 int GetOutputTimes() const; 9 10 private: 11 mutable int m_iTimes; 12 }; 13 14 ClxTest::ClxTest() 15 { 16 m_iTimes = 0; 17 } 18 19 ClxTest::~ClxTest() 20 {} 21 22 void ClxTest::Output() const 23 { 24 cout << "Output for test!" << endl; 25 m_iTimes++; 26 } 27 28 int ClxTest::GetOutputTimes() const 29 { 30 return m_iTimes; 31 } 32 33 void OutputTest(const ClxTest& lx) 34 { 35 cout << lx.GetOutputTimes() << endl; 36 lx.Output(); 37 cout << lx.GetOutputTimes() << endl; 38 }
計數器m_iTimes被mutable修飾,那么它就可以突破const的限制,在被const修飾的函數里面也能被修改。mutable只能作用在類成員上,指示其數據總是可變的。不能和const 同時修飾一個成員,但能配合使用:const修飾的方法中,mutable修飾的成員數據可以發生改變,除此之外不應該對類/對象帶來副作用。考慮一個mutable的使用場景:呼叫系統中存有司機(Driver)的信息,為了保護司機的隱私,司機對外展現的聯系號碼每隔五分鍾從空閑號碼池更新一次。
const方法中不允許對常規成員進行變動,但mutable成員不受此限制。對Driver類來說,其固有屬性(姓名、年齡、真實手機號等)未發生改變,符合const修飾。mutable讓一些隨時可變的展示屬性能發生改變,達到了靈活編程的目的。
二、volatile關鍵字
volatile原意是“易變的”,但這種解釋簡直有點誤導人,應該解釋為“直接存取原始內存地址”比較合適。“易變”是相對與普通變量而言其值存在編譯器(優化功能)未知的改變情況(即不是通過執行代碼賦值改變其值的情況),而是因外在因素引起的,如多線程,中斷等。編譯器進行優化時,它有時會取一些值的時候,直接從寄存器里進行存取,而不是從內存中獲取,這種優化在單線程的程序中沒有問題,但到了多線程程序中,由於多個線程是並發運行的,就有可能一個線程把某個公共的變量已經改變了,這時其余線程中寄存器的值已經過時,但這個線程本身還不知道,以為沒有改變,仍從寄存器里獲取,就導致程序運行會出現未定義的行為。並不是因為用volatile修飾了的變量就是“易變”了,假如沒有外因,即使用volatile定義,它也不會變化。而加了volatile修飾的變量,編譯器將不對其相關代碼執行優化,而是生成對應代碼直接存取原始內存地址。
volatile用於修飾成員或變量,指示其修飾對象可能隨時變化,編譯器不要對所修飾變量進行優化(緩存),每次取值應該直接讀取內存。由於volatile的變化來自運行期,其可以與const一起使用。兩者一起使用可能讓人費解,如果考慮場景就容易許多:CPU和GPU通過映射公用內存中的同一塊,GPU可能隨時往共享內存中寫數據。對CPU上的程序來說,const修飾變量一直是右值,所以編譯通過。但其變量內存中的值在運行期間可能隨時在改變,volatile修飾是正確做法。
在多線程環境下,volatile可用作內存同步手段。例如多線程爆破密碼:
1 volatile bool found = false; 2 void run(string target) { 3 while (!found) { 4 // 計算字典口令的哈希 5 if (target == hash) { 6 found = true; break; 7 } 8 } 9 }
在volatile的修飾下,每次循環都會檢查內存中的值,達到同步的效果。需要注意的是,volatile的值可能隨時會變,期間會導致非預期的結果。例如下面的例子求平方和:
1 double square(volatile double a, volatile double b) { 2 return (a + b) * (a + b); 3 }
一般說來,volatile用在如下的幾個地方:
1、中斷服務程序中修改的供其它程序檢測的變量需要加volatile;
2、多任務環境下各任務間共享的標志應該加volatile;
3、存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能有不同意義;
總結
mutable只能用與類變量,不能與const同時使用;在const修飾的方法中,mutable變量數值可以發生改變;
volatile只是運行期變量的值隨時可能改變,這種改變即可能來自其他線程,也可能來自外部系統。
參考
1. https://en.cppreference.com/w/cpp/language/cv
2. https://blog.csdn.net/aaa123524457/article/details/80967330