《Effective C++》读书笔记 条款03 尽可能使用const 使代码更加健壮
如果你对const足够了解,只需记住以下结论即可:
- 将某些东西声明为const可帮助编译器侦测出错误用法,const可被施加于任何作用于内的对象、函数参数、函数返回类型、成员函数本体。
- 编译器强制实施bitwise constness,但你编写程序时应该使用概念上的常量性(logical constness)
- 当const和non-const成员函数有实质等价的实现时,令non-const版本调用const版本可避免代码重复
关键字const允许你指定一个语义约束,即指定一个对象不被改动,编译器会强制实施这项约束。只要你确信某个对象不该被改变(赋值操作等),你就应该将他指定为const。
值得注意的是cosnt与指针的关系,需要记住的是如果const出现在星号左边,表示被指物是常量,出现在右边则指指针自身是常量,如果两边都有,则表示被指物和指针都是常量。
- char greeting[] = "hello";
- const char* p = greeting;//常量对象,非常量指针
- char* const p = greering;//常量指针,非常量对象
- const char* const p = greeting;//常量指针,常量对象
- char const* p = greeting;//仍然是常量对象,非常量指针(注意char和const的位置)
const成员函数
将const用于成员函数有两个理由
- 使class接口容易被理解,因为可以得知哪个函数可以改动对象类型,哪个不行
- 使操作const对象成为可能
const成员函数可以处理取得(并经修饰而成的cosnt对象),即const成员函数可以返回一个const对象。
另外需要注意的是,如果函数的返回类型是个内置类型,那么改动函数返回值就不合法,返回值是const时也是如此。
bitwise(physical) constness和logical constness
bitwise constness是C++对常量性的定义,其含义是对象内的任意一个bit都不允许更改。所以const成员函数不可以更改对象内任何非static成员变量
但是某些情况引发了异议:一个更改了指针所指物的成员函数虽然不能算是const,但如果只有指针隶属于对象,那么称此函数为bitwise const不会引发编译器异议
- class CTextBlock
- {
- public:
- char& operator[](std::size_t position) const//bitwise cosnt声明,此const表示该成员函数隐含的this指针为const
- {
- return pText[position];//该操作可能更改指针所指物,但并不会引发编译器异议
- }
- private:
- char* pText;
- };
针对这种情况引发了logical constness概念,即const成员函数可以在编译器侦测不出的情况下修改它所处理的对象内的某些bit
如果想在const成员函数内修改对象成员变量,则必须绕开bitwise constness,这种情况下可以使用mutable关键字,mutable释放掉非static成员变量的bitwise constness约束。即可以在const成员函数内改变对象成员变量。
- class CTextBlock
- {
- public:
- std::size_t length() const;
- private:
- char* pText;
- mutable std::size_t textLength;//使用了mutable关键字
- mutable bool lengthIsValid;
- };
- std::size_t length() const
- {
- if(!lengthIsValid)
- {
- textLength = std::strlen(pText);//可以在const成员函数内随意改变mutable修饰的成员变量
- lengthIsValid = true;
- }
- return textLength;
- }
在const和non-const成员函数中避免重复
如果一个函数的const和non-const版本所做的工作相同,则可以在non-const版本中使用const版本函数来避免代码重复
- class TextBlock
- {
- public:
- const char& operator[](std::size_t positon) const
- {
- return text[position];
- {
- char & operator[](std::size_t position)
- {
- return
- const_cast<char&>(
- static_cast<const TextBlock&>(*this)
- [position];
- );
- }
- };
const版本的函数可能有点难于理解,但他只进行了两次强制类型转换。一次类型转换是为了明确指出调用的是const operator[],因为必须使用static_cast将*this类型转型为const TextBlock&,第二次使用const_cast移除返回值的const属性,使其与函数声明一致。
反过来,在const函数中调用其non-const版本是不合法的,因为const函数承诺绝不改变其对象的逻辑状态。
ps: 命名的强制类型转换的知识
- static_cast:
- 任何具有明确定义的类型转换,只要不包含底层const(即指所指对象是常量的const),都可以使用static_cast。
- 特别地,当我们把指针存放在void*中,并且使用static_cast将其强制转换回原来类型时,应该确保指针的值保持不变,也就是说,强制转换的结果将与
- 原始地址值相等。因此,必须确保转换后所得的类型就是指针所指的类型,否则,类型一旦不符,将产生未定义的后果。
- const_cast:
- 只能改变运算对象的底层const。需要注意的是,如果对象本身是个常量,使用const_cast会产生未定义的后果,除非你知道自己在做什么,否则不要乱用。
- 使用const_cast做除了改变表达式常量属性以外的转换都将引发编译器错误
- dynamic_cast:
- dynamic_cast用于将基类指针或引用安全地转换成派生类的指针或引用,使用形式如下
- dynamic_cast<type*>(e)