一、const 成员函数

首先来复习一下 const 成员函数,咱们自己实现一个字符串 MyStr,内部运用 char* 指针保存原始数据,运用 get_length() 函数获取数组长度,因为 get 函数不会修正成员变量,能够运用 const 关键字润饰 get_length() 函数,编译期就会帮咱们查看是否修正了成员变量,如果有修正,就会在编译期报错,这样就能够维护成员变量不被修正。

class MyStr {
public:
  MyStr() {...}
  ~MyStr() {...}
  size_t get_length() const {
    // _length = 1;  编译犯错
    return _length;
  }
private:
  char* _data = nullptr;
  size_t _length = 0;
}

二、logic const 和 bitwise const

编译器是怎么实现 const 成员查看的呢?在编译时它会查看该函数有没有对成员变量进行修正,比方上面代码里的赋值操作 _length = 1 就明显修正了成员变量,发现这样的操作编译器就会报警,这种校验就是 bitwise const。

但是只有 bitwise const 是不够的,想象 MyStr 中的 _data 运用了高速缓存,还有别的操作会修正字符串的长度,get_length() 函数需求改成下面这样。

size_t get_length() const {
  _length = strlen(_data); // 编译器会报错
  return _length;
}

更新字符串长度并没有改变 _data 的内容,从逻辑(logic)来讲符合 const 函数的定义,但编译器并不这样以为,咱们能够运用 mutable 字段让编译器不再阻拦咱们,像下面这样

MyStr {
public:
  MyStr() {...}
  ~MyStr() {...}
  size_t get_length() const {
    _length = strlen(_data); // 编译器不会报错
    return _length;
  }
private:
  char* _data = nullptr;
  mutable size_t _length = 0;
}

这种逻辑上不改变数据的 const 函数就叫做 logic const。

三、编译器查看不出怎么办

上面讲了一种绕过编译器查看的状况,下面来看一种编译器查看不出来的问题,当类的成员变量为指针时,只改变指针指向的数据而不改变指针,编译器是不会以为违背 const 准则的。比方咱们供给 operater[] 操作符来访问 MyStr 的元素

class MyStr {
public:
  ...
  char& operator[](const size_t index) const {
    return _data[index];
  }
private:
  char* _data = nullptr;
  size_t _length = 0;
}
MyStr my_str;
my_str[0] = 'a'; // 这样就经过const函数改变了成员变量指针指向的内容,而且编译器不会报错

这种状况怎么办呢?咱们一定要记得把 const 函数的返回值设置为 const

class MyStr {
public:
  ...
  const char& operator[](const size_t index) const {
    return _data[index];
  }
private:
  char* _data = nullptr;
  size_t _length = 0;
}
MyStr my_str;
my_str[0] = 'a'; // 这样编译器就会报错,因为咱们修正了常引证

总结

最终把前面讲的总结为两点:

  1. 想要绕过编译器 const 查看时,对成员变量增加 mutable 润饰
  2. 常量函数的返回值(特别是引证和指针)一定要加 const 润饰