c++中的 mutable关键字

mutable 是用来修饰一个 const 示例的部分可变的数据成员的。如果要说得更清晰一点,就是说 mutable 的出现,将 C++ 中的 const 的概念See cppreference cv 分成了两种:

  • 二进制层面的 const,也就是「绝对的」常量,在任何情况下都不可修改(除非用 const_cast)

  • 引入 mutable 之后,C++ 可以有逻辑层面的 const,也就是对一个常量实例来说,从外部观察,它是常量而不可修改;但是内部可以有非常量的状态。当然,所谓的「逻辑 const」,在 C++ 标准中并没有这一称呼。这只是为了方便理解,而创造出来的名词。

显而易见,mutable 只能用来修饰类的数据成员;而被 mutable 修饰的数据成员,可以在 const 成员函数中修改。

比如:

class HashTable {
 public:
    //...
    std::string lookup(const std::string& key) const
    {
        if (key == last_key_) {
            return last_value_;
        }

        std::string value{this->lookupInternal(key)};

        last_key_   = key;
        last_value_ = value;

        return value;
    }

 private:
    mutable std::string last_key_
    mutable std::string last_value_;
};

lookup 函数是const 的函数,正常来说是不能修改成员变量的,但是加上mutable之后,就可以修改了。

更实际的例子是用在锁上面。比如:

class Queue {
 public:
    void push(int x);
    bool empty() const;
};

假设这是一个线程安全的队列,empty函数是const的函数 不能修改内部成员,如果有锁,也就不能修改锁的状态,这样也就无法实现线程安全了。mutable可以实现这种情况。

class Queue {
private:
    mutable std::mutex mutex_;
 public:
    void push(int x);
    bool empty() const {
        std::lock_guard<std::mutex> lk(mutex_);
    }
};

还有一种情况使用mutable, 就是lambda函数:

在 Lambda 表达式的设计中,捕获变量有几种方式;其中按值捕获(Caputre by Value)的方式不允许程序员在 Lambda 函数的函数体中修改捕获的变量。而以 mutable 修饰 Lambda 函数,则可以打破这种限制。

int x{0};
auto f1 = [=]() mutable {x = 42;};  // okay, 创建了一个函数类型的实例
auto f2 = [=]()         {x = 42;};  // error, 不允许修改按值捕获的外部变量的值

需要注意的是,上述 f1 的函数体中,虽然我们给 x 做了赋值操作,但是这一操作仅只在函数内部生效——即,实际是给拷贝至函数内部的 x 进行赋值——而外部的 x 的值依旧是 0。