C++ 的关键字 mutable

mutable 的特性

mutable 是个很有意思的属性。mutable 声明的类成员可以在 const 方法中被修改;const 实例中的 mutable 成员不受 const 的限制。

见如下实例代码:


class C { public: C():x(1),y(2){} void func() const // 注意这里的 const { x = 3; // error: read-only variable is not assignable y = 4; // ok } int x; mutable int y; }; int main() { const C c; // 这里也有 const c.x = 5; // error: read-only variable is not assignable c.y = 6; // ok c.func(); return 0; }

上例中,y 有 mutable 属性,所以它可以在 const 方法 func() 中被修改,并且在具有 const 属性的 c 中也可以被修改。

我们可以这样理解:对 mutable 成员的修改不影响类或实例的 const 状态。

适用场景

那么, mutable 的适用场景有哪些呢?我能想到的有两个。下面通过一个例子把这两种适用场景呈现给大家。

比如,我们有一个容器类 C,它有两个方法: size() 可以得到当前容器中含有的项目数;capacity()可以得到当前容器的容量。另外,我们有个函数 status(),它接收一个 C 的实例作为参数,并通过访问 size()capacity() 把该实例的状态打印出来。我们还有个要求,status() 不会改变 C 实例的内容,所以它的参数应该是 const 的。

代码如下:

class C
{
public:
    C():_size(0),_capacity(0){}

    int size() const // const 允许 status() 对其访问
    {
        return _size;
    }

    int capacity() const // const 允许 status() 对其访问
    {
        return _capacity;
    }

private:
    int _size;
    int _capacity;
};

void status(const C &c) // const 保证 status 不会改变 c 的内容
{
    printf("size=%d, capacity=%d\n", c.size(), c.capacity());
}

上面代码应该是没问题的。下面我们通过加入一些限制来构造我们的适用场景。

适用场景一

先来看 capacity()。由于某些原因,我们在初始化时并没有初始化成员 _capacity。我们希望只有当 capacity() 被调用时我们才计算容量的值,并且这个值一旦得到将不再改变。

应该这样实现:

class C
{
    //...

    int capacity() const
    {
        if (_capacity == 0)
        {
            _capacity = ...; // 计算容量值,修改 _capacity
        }
        return _capacity;
    }

private:
    int _size;
    mutable int _capacity; // 因为需要在 const 方法中被修改,所以必须有 mutable 属性
};

这就是我们的第一个适用场景:当第一次请求时需要计算并缓存,以后只需访问缓存中的值。

适用场景二

再来看 size()。容器中含有的项目数是时刻在变化的,为了保证线程安全,我们需要在访问 _size 之前加锁。这不难,我们只需在容器 C 中增加个新成员 _lock 即可。但加锁这个动作会改变 _lock,而对 size() 的 const 声明要求在 size() 中不能修改类成员。这时就需要给 _lock 加 mutable 属性了。

这就是我们的第二个适用场景:const 方法需要修改类成员,而这种修改不需要、也不应该对调用者可见。

实现代码如下:

class C
{
public:
    C():_size(0),_capacity(0){}

    //...

    int size() const
    {
        int s = 0;

        _lock.lock(); // lock() 会修改 _lock
        s = _size;
        _lock.unlock();

        return s;
    }

private:
    int _size;
    mutable Lock _lock; // 因为需要在 const 方法中被修改,所以必须有 mutable 属性
};

C++11 中的 mutable

在 C++11 中,mutable 还会用在 Lambda 表达式中,用来允许执行体修改捕获的参数。Lambda 表达式默认是 const 属性的,故不能修改捕获的参数。

{
    int x = 0;
    auto l1 = [=]() mutable { x = 1; };
    auto l2 = [=]()         { x = 2; }; // error: cannot assign to a variable captured by copy in a non-mutable lambda

    // ...
}

总结

  • mutable 声明的类成员可以在 const 方法中被修改。
  • const 实例中的 mutable 成员不受 const 的限制。
  • 适用场景一:当第一次请求时需要计算并缓存,以后只需访问缓存中的值。
  • 适用场景二:const 方法需要修改类成员,而这种修改不需要、也不应该对调用者可见。
  • 在 C++11 中,mutable 还会用在 Lambda 表达式中,用来允许执行体修改捕获的参数。
Creative Commons License Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 4.0 International license .