服务器之家

服务器之家 > 正文

C++中mutable与volatile的深入理解

时间:2021-07-10 15:47     来源/作者:tlanyan

前言

C++中修饰数据可变的关键字有三个:const、volatilemutable。const比较好理解,表示其修饰的内容不可改变(至少编译期不可改变),而volatile和mutable恰好相反,指示数据总是可变的。mutable和volatile均可以和const搭配使用,但两者在使用上有比较大差别。

下面话不多说了,来一起看看详细的介绍吧

mutable

mutable只能作用在类成员上,指示其数据总是可变的。不能和const 同时修饰一个成员,但能配合使用:const修饰的方法中,mutable修饰的成员数据可以发生改变,除此之外不应该对类/对象带来副作用。

考虑一个mutable的使用场景:呼叫系统中存有司机(Driver)的信息,为了保护司机的隐私,司机对外展现的联系号码每隔五分钟从空闲号码池更新一次。根据需求,Driver类的实现如下伪代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Driver {
private:
...
// real phone number
string phone;
// display phone number
mutable string displayPhone;
 
public:
string getDisplayPhone() const {
if (needUpdate()) {
lock.lock();
if (needUpdate()) {
updateDisplayPhone(); // displayPhone在这里被改变
}
lock.unlock();
}
return displayPhone;
}
};

在上述代码中,const方法中不允许对常规成员进行变动,但mutable成员不受此限制。对Driver类来说,其固有属性(姓名、年龄、真实手机号等)未发生改变,符合const修饰。mutable让一些随时可变的展示属性能发生改变,达到了灵活编程的目的。

volatile

volatile用于修饰成员或变量,指示其修饰对象可能随时变化,编译器不要对所修饰变量进行优化(缓存),每次取值应该直接读取内存。由于volatile的变化来自运行期,其可以与const一起使用。两者一起使用可能让人费解,如果考虑场景就容易许多:CPU和GPU通过映射公用内存中的同一块,GPU可能随时往共享内存中写数据。对CPU上的程序来说,const修饰变量一直是右值,所以编译通过。但其变量内存中的值在运行期间可能随时在改变,volatile修饰是正确做法。

在多线程环境下,volatile可用作内存同步手段。例如多线程爆破密码:

?
1
2
3
4
5
6
7
8
9
10
11
volatile bool found = false;
 
void run(string target) {
while (!found) {
// 计算字典口令的哈希
if (target == hash) {
found = true;
break;
}
}
}

在volatile的修饰下,每次循环都会检查内存中的值,达到同步的效果。

需要注意的是,volatile的值可能随时会变,期间会导致非预期的结果。例如下面的例子求平方和:

?
1
2
3
double square(volatile double a, volatile double b) {
return (a + b) * (a + b);
}

a和b都是随时可变的,所以上述代码中的第一个a + b可能和第二个不同,导致出现非预期的结果。这种情况下,正确做法是将值赋予常规变量,然后再相乘:

?
1
2
3
4
double square(volatile double a, volatile double b) {
double c = a + b;
return c * c;
}

一般说来,volatile用在如下的几个地方:

1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;

2. 多任务环境下各任务间共享的标志应该加volatile;

3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;

总结

mutable只能用与类变量,不能与const同时使用;在const修饰的方法中,mutable变量数值可以发生改变;
volatile只是运行期变量的值随时可能改变,这种改变即可能来自其他线程,也可能来自外部系统。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

参考

https://en.cppreference.com/w/cpp/language/cv

下面是其他网友的补充

C/C++中的volatile关键字和const对应,用来修饰变量,用于告诉编译器该变量值是不稳定的,可能被更改。使用volatile注意事项:

(1). 编译器会对带有volatile关键字的变量禁用优化(A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided)。

(2). 当多个线程都要用到某一个变量且该变量的值会被改变时应该用volatile声明,该关键字的作用是防止编译器优化把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么多个线程有可能有的使用内存中的变量,有的使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中取出,而不是使用已经存在寄存器中的值(It cannot cache the variables in register)。

(3). 中断服务程序中访问到的变量最好带上volatile。

(4). 并行设备的硬件寄存器的变量最好带上volatile。

(5). 声明的变量可以同时带有const和volatile关键字。

(6). 多个volatile变量间的操作,是不会被编译器交换顺序的,能够保证volatile变量间的顺序性,编译器不会进行乱序优化(The value cannot change in order of assignment)。但volatile变量和非volatile变量之间的顺序,编译器不保证顺序,可能会进行乱序优化。

C++中的mutable关键字使用场景:

(1). 允许即使包含它的对象被声明为const时仍可修改声明为mutable的类成员(sometimes there is requirement to modify one or more data members of class/struct through const function even though you don't want the function to update other members of class/struct. This task can be easily performed by using mutable keyword)。

(2). 应用在C++11 lambda表达式来表示按值捕获的值是可修改的,默认情况下是不可修改的,但修改仅在lambda式内有效(since c++11 mutable can be used on a lambda to denote that things captured by value are modifiable (they aren't by default))。

详细用法见下面的测试代码,下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#include "volatile_mutable.hpp"
#include <iostream>
#include <stdio.h>
#include <time.h>
#include <mutex>
#include <string.h>
 
namespace volatile_mutable_ {
 
///////////////////////////////////////////////////////////
int test_volatile_1()
{
    volatile int i1 = 0; // correct
    int volatile i2 = 0; // correct
 
    return 0;
}
 
///////////////////////////////////////////////////////////
// reference: https://en.cppreference.com/w/c/language/volatile
int test_volatile_2()
{
{ // Any attempt to read or write to an object whose type is volatile-qualified through a non-volatile lvalue results in undefined behavior
    volatile int n = 1; // object of volatile-qualified type
    int* p = (int*)&n;
    int val = *p; // undefined behavior in C, Note: link does not report an error under C++
    fprintf(stdout, "val: %d\n", val);
}
 
{ // A member of a volatile-qualified structure or union type acquires the qualification of the type it belongs to
    typedef struct ss { int i; const int ci; } s;
    // the type of s.i is int, the type of s.ci is const int
    volatile s vs = { 1, 2 };
    // the types of vs.i and vs.ci are volatile int and const volatile int
}
 
{ // If an array type is declared with the volatile type qualifier (through the use of typedef), the array type is not volatile-qualified, but its element type is
    typedef int A[2][3];
    volatile A a = { {4, 5, 6}, {7, 8, 9} }; // array of array of volatile int
    //int* pi = a[0]; // Error: a[0] has type volatile int*
    volatile int* pi = a[0];
}
 
{ // A pointer to a non-volatile type can be implicitly converted to a pointer to the volatile-qualified version of the same or compatible type. The reverse conversion can be performed with a cast expression
    int* p = nullptr;
    volatile int* vp = p; // OK: adds qualifiers (int to volatile int)
    //p = vp; // Error: discards qualifiers (volatile int to int)
    p = (int*)vp; // OK: cast
}
 
{ // volatile disable optimizations
    clock_t t = clock();
    double d = 0.0;
    for (int n = 0; n < 10000; ++n)
        for (int m = 0; m < 10000; ++m)
            d += d * n*m; // reads and writes to a non-volatile
    fprintf(stdout, "Modified a non-volatile variable 100m times. Time used: %.2f seconds\n", (double)(clock() - t) / CLOCKS_PER_SEC);
 
    t = clock();
    volatile double vd = 0.0;
    for (int n = 0; n < 10000; ++n)
        for (int m = 0; m < 10000; ++m)
            vd += vd * n*m; // reads and writes to a volatile
    fprintf(stdout, "Modified a volatile variable 100m times. Time used: %.2f seconds\n", (double)(clock() - t) / CLOCKS_PER_SEC);
}
 
    return 0;
}
 
///////////////////////////////////////////////////////////
// reference: https://en.cppreference.com/w/cpp/language/cv
int test_volatile_3()
{
    int n1 = 0;      // non-const object
    const int n2 = 0;   // const object
    int const n3 = 0;   // const object (same as n2)
    volatile int n4 = 0; // volatile object
    const struct {
        int n1;
        mutable int n2;
    } x = { 0, 0 };   // const object with mutable member
 
    n1 = 1; // ok, modifiable object
    //n2 = 2; // error: non-modifiable object
    n4 = 3; // ok, treated as a side-effect
    //x.n1 = 4; // error: member of a const object is const
    x.n2 = 4; // ok, mutable member of a const object isn't const
 
    const int& r1 = n1; // reference to const bound to non-const object
    //r1 = 2; // error: attempt to modify through reference to const
    const_cast<int&>(r1) = 2; // ok, modifies non-const object n1
    fprintf(stdout, "n1: %d\n", n1); // 2
 
    const int& r2 = n2; // reference to const bound to const object
    //r2 = 2; // error: attempt to modify through reference to const
    const_cast<int&>(r2) = 2; // undefined behavior: attempt to modify const object n2, Note: link does not report an error under C++
    fprintf(stdout, "n2: %d\n", n2); // 0
 
    return 0;
}
 
///////////////////////////////////////////////////////////
// reference: https://www.geeksforgeeks.org/understanding-volatile-qualifier-in-c/
int test_volatile_4()
{
{
    const int local = 10;
    int *ptr = (int*)&local;
    fprintf(stdout, "Initial value of local : %d \n", local); // 10
 
    *ptr = 100;
    fprintf(stdout, "Modified value of local: %d \n", local); // 10
}
 
{
    const volatile int local = 10;
    int *ptr = (int*)&local;
    fprintf(stdout, "Initial value of local : %d \n", local); // 10
 
    *ptr = 100;
    fprintf(stdout, "Modified value of local: %d \n", local); // 100
}
 
    return 0;
}
 
///////////////////////////////////////////////////////////
// reference: https://en.cppreference.com/w/cpp/language/cv
int test_mutable_1()
{
    // Mutable is used to specify that the member does not affect the externally visible state of the class (as often used for mutexes,
    // memo caches, lazy evaluation, and access instrumentation)
    class ThreadsafeCounter {
    public:
        int get() const {
            std::lock_guard<std::mutex> lk(m);
            return data;
        }
        void inc() {
            std::lock_guard<std::mutex> lk(m);
            ++data;
        }
 
    private:
        mutable std::mutex m; // The "M&M rule": mutable and mutex go together
        int data = 0;
    };
 
    return 0;
}
 
///////////////////////////////////////////////////////////
// reference: https://www.tutorialspoint.com/cplusplus-mutable-keyword
int test_mutable_2()
{
    class Test {
    public:
        Test(int x = 0, int y = 0) : a(x), b(y) {}
 
        void seta(int x = 0) { a = x; }
        void setb(int y = 0) { b = y; }
        void disp() { fprintf(stdout, "a: %d, b: %d\n", a, b); }
 
    public:
        int a;
        mutable int b;
    };
 
    const Test t(10, 20);
    fprintf(stdout, "t.a: %d, t.b: %d \n", t.a, t.b); // 10, 20
 
    //t.a=30; // Error occurs because a can not be changed, because object is constant.
    t.b = 100; // b still can be changed, because b is mutable.
    fprintf(stdout, "t.a: %d, t.b: %d \n", t.a, t.b); // 10, 100
 
    return 0;
}
 
///////////////////////////////////////////////////////////
// reference: https://www.geeksforgeeks.org/c-mutable-keyword/
int test_mutable_3()
{
    using std::cout;
    using std::endl;
 
    class Customer {
    public:
        Customer(char* s, char* m, int a, int p)
        {
            strcpy(name, s);
            strcpy(placedorder, m);
            tableno = a;
            bill = p;
        }
 
        void changePlacedOrder(char* p) const { strcpy(placedorder, p); }
        void changeBill(int s) const { bill = s; }
 
        void display() const
        {
            cout << "Customer name is: " << name << endl;
            cout << "Food ordered by customer is: " << placedorder << endl;
            cout << "table no is: " << tableno << endl;
            cout << "Total payable amount: " << bill << endl;
        }
 
    private:
        char name[25];
        mutable char placedorder[50];
        int tableno;
        mutable int bill;
    };
 
    const Customer c1("Pravasi Meet", "Ice Cream", 3, 100);
    c1.display();
    c1.changePlacedOrder("GulabJammuns");
    c1.changeBill(150);
    c1.display();
 
    return 0;
}
 
///////////////////////////////////////////////////////////
// reference: https://stackoverflow.com/questions/105014/does-the-mutable-keyword-have-any-purpose-other-than-allowing-the-variable-to
int test_mutable_4()
{
    int x = 0;
    auto f1 = [=]() mutable { x = 42; }; // OK
    //auto f2 = [=]() { x = 42; }; // Error: a by-value capture cannot be modified in a non-mutable lambda
    fprintf(stdout, "x: %d\n", x); // 0
 
    return 0;
}
 
} // namespace volatile_mutable_

GitHub:https://github.com/fengbingchun/Messy_Test

原文链接:https://www.tlanyan.me/mutable-and-volatile-in-cpp/

标签:

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总 2020-11-13
2021德云社封箱演出完整版 2021年德云社封箱演出在线看
2021德云社封箱演出完整版 2021年德云社封箱演出在线看 2021-03-15
返回顶部