mutable
用于修饰类的成员变量,表示该成员变量可以在被标记为 const 的成员函数内修改。
1、const成员函数
在 C++ 中,将成员函数标记为 const 有两个主要目的:
-
约定和安全性: 使用 const 关键字是为了表达一个承诺,即该成员函数不会修改对象的状态,这增加了对于程序员来说的可读性。在 const 成员函数内部,编译器也会强制禁止修改非 mutable 的成员变量。
-
允许 const 对象调用: const 成员函数可以被 const 对象调用,而非 const 成员函数不能被const对象调用。
-
假设有一个类 Person:
class Person { public: // 普通成员函数 void setName(const std::string& newName) { name = newName; } // const 成员函数 std::string getName() const { return name; } private: std::string name; };
在这个例子中,
setName
是一个普通的成员函数,而getName
被标记为 const。现在,假设你创建了一个 常对象和一个普通对象,并用这两个对象调用普通成员函数SetName
,很明显常量对象不能调用普通成员函数(这样做违反了 const 对象的语义,即不修改对象的外部状态。)const Person constPerson; Person nonConstPerson; constPerson.setName("Alice"); // 错误,const 对象不能调用非 const 成员函数 nonConstPerson.setName("Bob"); // 正确
尝试使用这两个对象调用常函数
getName
,都可以成功:std::cout << constPerson.getName() << std::endl; // 正确,const 对象可以调用 const 成员函数 std::cout << nonConstPerson.getName() << std::endl; // 正确
-
2、使用mutable的场景举例
考虑这样的场景如何实现:有一个狗,它可以狗叫,并且记录狗叫次数,这个狗可以是const狗,也可以是非const狗
#include <iostream>
class Dog {
public:
Dog() {}
void Barking(){
std::cout << "我在狗叫" << "\n";
++count;
}
int GetBarkingCount() const {
return count;
}
private:
int count = 0;
};
int main()
{
// 创建普通狗,并狗叫
Dog nonConstDog;
nonConstDog.Barking();
nonConstDog.Barking();
std::cout << "普通狗狗叫次数" << nonConstDog.GetBarkingCount() << "\n"; // 2次
// 创建常量狗,并狗叫
const Dog constDog;
constDog.Barking(); // error 常量狗不能用普通狗叫,
std::cout << "常量狗狗叫次数 " << constDog.GetBarkingCount() << "\n";
return 0;
}
可以看到,如果这样写代码,常量狗是不配狗叫的,如果用上mutable
,并且把狗叫函数改为const
,就能很方便的实现了
class Dog {
public:
Dog() {}
void Barking() const {
std::cout << "我在狗叫" << "\n";
++count;
}
int GetBarkingCount() const {
return count;
}
private:
mutable int count = 0;
};
lambda表达式中,如果没有为lambda指定mutable,则不能修改捕获变量的值。如果捕获的是一个对象,那么在函数体内部不能调用捕获对象的非const方法,因为非const方法是可能修改改对象的状态的
#include <iostream>
class Example
{
public:
void nonConstMethod()
{
std::cout << "Non-const method called." << std::endl;
}
};
int main()
{
Example obj;
// 没有使用 mutable,尝试调用对象的非 const 方法会导致编译错误
auto lambda = [obj]()
{
// obj.nonConstMethod(); // 这一行会导致编译错误,因为默认情况下捕获的对象是 const 的
std::cout << "Inside lambda." << std::endl;
};
lambda(); // 调用 lambda 表达式
return 0;
}
所以这里必须给lambda表达式加一个mutable
auto lambda = [obj]() mutable
{
obj.nonConstMethod();
std::cout << "Inside lambda." << std::endl;
};
3、侵入式引用计数
std::shared_ptr
是非侵入式引用计数,即使用的外部引用计数器。
一个基类,所有需要引用计数的类都需要继承自它:
class RefCounted
{
public:
virtual ~RefCounted() = default;
void IncRefCount() const
{
++m_RefCount;
}
void DecRefCount() const
{
--m_RefCount;
}
uint32_t GetRefCount() const { return m_RefCount.load(); }
private:
mutable std::atomic<uint32_t> m_RefCount = 0;
};
自定义的智能指针类,仅仅是一个包装,内部维护了一个指针,其指向一个带引用计数的资源对象:mutable T* m_Instance
template<typename T>
class Ref
{
public:
Ref() : m_Instance(nullptr) {}
Ref(std::nullptr_t n) : m_Instance(nullptr) {}
Ref(T* instance)
: m_Instance(instance)
{
static_assert(std::is_base_of<RefCounted, T>::value, "Class is not RefCounted!");
IncRef();
}
// 拷贝构造
Ref(const Ref<T>& other)
: m_Instance(other.m_Instance)
{
IncRef();
}
~Ref()
{
DecRef();
}
Ref& operator=(std::nullptr_t)
{
DecRef();
m_Instance = nullptr;
return *this;
}
Ref& operator=(const Ref<T>& other)
{
if (this == &other)
return *this;
other.IncRef();
DecRef();
m_Instance = other.m_Instance;
return *this;
}
// 堆区创建T类型资源,把形参全部给他,并同时构造一个Ref<T>类型的"临时"对象返回
// 完美转发
template<typename... Args>
static Ref<T> Create(Args&&... args)
{
return Ref<T>(new T(std::forward<Args>(args)...));
}
private:
void IncRef() const
{
if (m_Instance)
{
m_Instance->IncRefCount();
RefUtils::AddToLiveReferences((void*)m_Instance);
}
}
void DecRef() const
{
if (m_Instance)
{
m_Instance->DecRefCount();
if (m_Instance->GetRefCount() == 0)
{
delete m_Instance;
m_Instance = nullptr;
}
}
}
mutable T* m_Instance;
};
使用方式:
class Image2D: public RefCounted
{
public:
Image2D(std::string path)
{
// 加载并存储
}
// ......
}
int main() {
// 引用计数增加到1
Ref<Image2D> image1 = Ref<Image2D>::Create("path_to_image1.png");
{
// 引用计数增加到2
Ref<Image2D> image2 = image1;
} // image2 超出作用域,引用计数减少到1
return 0;
} // image1 超出作用域,引用计数减少到0,Image2D 对象被销毁