C++深入理解mutable和volatile關鍵字


  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM