mutable 关键字
mutable
理解
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属性。这三类场景的应用实例见第二部分。
典型场景
** 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;
}
** 内部缓存(非类的内部状态)**
内部缓存一般是与类的内部状态无关的,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;
}
*** 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
posted on 2021-06-16 16:10 Ultraman_X 阅读(208) 评论(0) 编辑 收藏 举报