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