1,理解
- mutable字面意思是可变的,其实直接定义的local variable都是可变的,所以mutable对于修饰普通的local variable是没有意义的。事实上,编译器会禁止你这么做:
#include <iostream>
int main() {
mutable int a{0};
a = 5;
std::cout << "a=" << a << std::endl;
}
编译报错:
mutable.cpp:4:17: error: 'mutable' can only be applied to member variables
由此可见,mutable用于修饰类的成员变量:
#include <iostream>
int main() {
struct mu_st {
mutable int a;
};
const mu_st ms{0};
ms.a = 5;
std::cout << "a=" << ms.a << std::endl;
}
这里,在一个类mu_st
中定义了一个mutable
的变量a,并实例化了一个const
的对象ms
,理论上,因为const
性,不允许改变ms
对象,但因为a被声明了mutable
,它就可以突破const
的限制,从而变成可变的。
这就是mutable
的作用,在const类
或const成员函数
中修改一些状态。到这里,不禁要问,声明为const就是为了防止改变对象,现在有搞出来个mutable,这不自相矛盾吗?
确实,上例中,mutable的应用是没有意义的,但在某些场合,mutable却是不可缺少的。其主要场景包括两类(参考 “Effective Modern C++”),第一是在const成员函数需要多线程并发时,第二是为了优化程序运行,做一些与类内部状态无关的缓存,第三类是在lambda表达式中去除采用赋值方式捕获的变量的const属性。这三类场景的应用实例见第二部分。
- cv属性
在进入实例之前,先简单说一下cv属性,cv是指const-volatile,它们都是c++的类型指定符(type specifier),用来指定变量的常性和可变性。而mutable通常用来突破const限制,使变量处于永远可变状态。
2,典型场景
mutex -- const函数的并发支持
c++中实现线程安全的通常做法是使用std::mutex
,但是在const成员函数中,对mutex的加锁和释放锁操作会违背const的不可变语义,所以,只能将mutex定义为mutable,从而可以在const修饰的函数中加锁,实现线程安全。
#include <iostream>
class Cal {
public:
Cal(int n) {num = n;}
void inc_num() {
std::lock_guard<std::mutex> lg(m);
++num;
}
int get_num() const {
std::lock_guard<std::mutex> lg(m);
return num;
}
private:
int num;
mutable std::mutex m;
};
int main() {
Cal c{0};
std::cout << c.get_num() << std::endl;
}
Cal类的get_num()函数保证了读操作的线程安全性。
内部缓存(非类的内部状态)
内部缓存一般是与类的内部状态无关的,const函数语义上表示不修改类的状态,但有时为了缓存某些计算结果、某些中间数据,需要在const函数中修改一些成员变量,这些变量并不影响类的状态,所以这种const叫做logic const.
#include <iostream>
class Account {
public:
float query() const {
++query_cnt;
return total;
}
float total; // 总账户
mutable int query_cnt; // 查询次数
};
int main() {
Account acc{100.0f};
acc.query();
acc.query();
std::cout << "query times: " << acc.query_cnt << std::endl;
}
这里,query_cnt与账户本身状态无关,可以认为query()本身声明为const的语义为不改变账户余额,因此,虽然查询次数缓存被改变,但并不影响语义上的const。
lambda表达式
当lambda表达式用[=]捕获变量时,在表达式内部是不允许修改变量的,但是可用mutable允许修改变量:
#include <iostream>
int main() {
int a = 0;
auto cb = [=]() mutable {
a = 1;
std::cout << "a=" << a << std::endl;
};
cb();
std::cout << "a=" << a << std::endl;
}
运行结果为:
a=1
a=0
注意虽然lambda表达式内改变了a的值,但实际上a是通过拷贝赋值的,main内定义的a是没有改变的。如果cb没有加mutable,则会出现编译错误:
mutable.cpp:6:11: error: cannot assign to a variable captured by copy in a non-mutable lambda
a = 1;