目录安全
关键字const能够用于如下对象。函数
STL迭代器以指针为根据塑模出来,因此STL迭代器的做用就像个T *指针。post
std::vector<int> vec; const std::vector<int>::iterator iter = vec.begin(); //iter至关于T *const指针 *iter = 10; //没问题 ++iter; //错误,iter不可修改 std::vector<int>::const_iterator const_iter = vec.begin(); //const_iter至关于const T *指针 *const_iter = 10; //错误,*const_iter不可修改 ++const_iter; //没问题
const最具威力的用法是面对函数声明时的应用。在一个函数声明中,const能够和函数参数、函数返回值、函数自身(若是是成员函数)产生关联。
const用于函数参数只需记住一条原则:除非函数体中须要改动参数,不然就将它们声明为const。
const用于函数返回值,每每能够下降因使用错误而形成的意外,同时又不至于放弃安全性和高效性,举个例子,看下面有理数类operator *的声明。this
class Rational { ... }; const Rational operator * (const Rational &lhs, const Rational &rhs);
Rational a, b, c; (a * b) = c; //有意错误,对两个数的乘积进行赋值,就比如1 = 2同样 if (a * b = c) //无心错误,将==漏写为=
将const用于成员函数的目的,是为了确认该成员函数可做用于const对象身上。const成员函数之因此重要,基于两个理由。编码
第2条对于编写高效代码是个关键,由于如条款20所述,改善C++程序效率的一个根本方法是以const引用的方式传递对象。
const引用的多是const对象,而const对象只能调用const成员函数。
因此此技术可行的前提是,有const成员函数可用来处理取得的const对象;不然,就算能将const对象传进来,也没有办法去处理它。spa
C++有一个重要特性:两个成员函数若是只是常量性不一样,则能够构成重载,即便它们的参数类型、参数个数、参数顺序都彻底一致。
基于这个特性,再结合上面提到的高效编码技巧,就能够得出以下所示的接口设计。设计
class TextBlock { private: std::string text; public: //用于const对象,因为const对象内容不容许修改,所以返回值也加了const const char &operator [] (std::size_t postion) const { return text[postion]; } //用于non-const对象 char &operator [] (std::size_t postion) { return text[postion]; } }; void print(const TextBlock &text) { std::cout << text[0]; } TextBlock text; const TextBlock const_text; print(text); //调用char &operator [] () print(const_text); //调用const char &operator [] () const
C++编译器要求const成员函数不能更改对象内的任何non-static成员变量,简单地说就是const成员函数中不能出现对non-static成员变量的赋值操做。
这种要求实质上是不能更改对象内的任何一个bit,所以叫作bitwise constness。
不幸的是,许多const成员函数虽然不彻底具有const性质,但却能经过C++编译器的bitwise检验,更具体地说,就是:指针
class TextBlock { private: char *pText; public: char &operator [] (std::size_t postion) const { return pText[postion]; } }; const TextBlock text("Hello"); //声明一个const对象 char *pc = &text[0]; //调用const char &operator []取得一个指针,指向text的数据 *pc = 'J'; //经过pc指针将text的数据改成了"Jello"
上面这个class将operator []声明为const成员函数,但却返回了一个reference指向对象内部数据,这种作法是错误的,条款28对此有深入讨论,咱们暂时先忽略它。
从编译器bitwise constness的角度看,上述代码不存在任何问题,但你终究仍是改变了const对象的值,这种状况导出所谓的logical constness。日志
logical constness指的是,const成员函数能够修改它所处理对象内的某些bits,但前提是用户察觉不到这种修改。
要想在const成员函数中修改non-static成员变量,须要对这些成员变量使用mutable
关键字,mutable能够去除non-static成员变量的bitwise constness约束。code
class CTextBlock { private: char *pText; mutable std::size_t textLength; //最近一次计算的文本长度 mutable bool lengthIsValid; //目前的长度是否有效 public: std::size_t length() const; }; std::size_t CTextBlock::length() const { if (!lengthIsValid) { textLength = std::strlen(pText); lengthIsValid = true; } return textLength; }
length()的实现固然不是bitwise constness,由于textLength和lengthIsValid均可能被修改,但这两个成员变量被修改对于const CTextBlock对象是能够接受的。
如今咱们对class TextBlock作一些修改,假设operator []不单只是返回一个reference指向某字符,还执行边界检查、日志数据访问、数据完整性检验等工做。
class TextBlock { private: std::string text; public: //用于const对象,因为const对象内容不容许修改,所以返回值也加了const const char &operator [] (std::size_t postion) const { ... //边界检查 ... //日志数据访问 ... //数据完整性检验 return text[postion]; } //用于non-const对象 char &operator [] (std::size_t postion) { ... //边界检查 ... //日志数据访问 ... //数据完整性检验 return text[postion]; } };
operator[]的const和non-const版本中的代码重复,可能会随着编译时间、持续维护、代码膨胀等因素而成为使人头痛的问题。
将重复代码封装到一个private函数中,并分别在两个函数中调用它,不失为一个解决该问题的好办法,但依然存在代码重复,如函数调用、return语句。
真正最好的办法是:先实现operator []的const版本,而后在non-const版本中调用它。以下示例代码所示,这种方法有两个技术要点。
class TextBlock { private: std::string text; public: //用于const对象,因为const对象内容不容许修改,所以返回值也加了const const char &operator [] (std::size_t postion) const { ... //边界检查 ... //日志数据访问 ... //数据完整性检验 return text[postion]; } //用于non-const对象 char &operator [] (std::size_t postion) { const TextBlock &const_this = static_cast<const TextBlock &>(*this); //将自身从TextBlock &转换为const TextBlock & return const_cast<char &>(const_this[postion]); //调用const版本的operator [],并去除返回值中的const属性,而后返回 } };
注意,千万不要令const版本调用ono-const版原本避免代码重复,由于const版本调用non-const版本的惟一方法是去除自身的const属性,这绝对不是个好事情。