《Effective C++》笔记:I
条款2:Prefer consts,enums,and inlines to #defines
译:尽量以const,enums,inline替换#define。
#define是预处理器中宏定义。
举个例子#define PI = 3.1415926,这句话就相当于把代码中所有出现的symbol在编译前替换成数值3.1415926 。
书中提到这种盲目替换,如果因某些原因在运用该常量时得到一个错误信息,这个错误信息会提到3.1415926而不是PI,而我们对3.1415926这个值以及其来自何处毫无概念,于是要花费大量时间追踪它。
原因就是编译器并没有把PI这个符号放入符号表(symbol table)中。
解决办法很简单,用一个常量替换这个宏:const double PI = 3.1415926。
书中还提到了了一个enumb hack用于数组声明式(编译器要求在编译期间知道数组的大小),用enum可以替换#define声明数组容量的场景。
enum hack是模板元编程的基础技术,这在条款48有详细介绍(不过我对模板元编程的了解知之甚少)
其实#define带来的麻烦并不只这些。最常见的就是宏定义一个函数,如#define Max(int a,int b) a>b?a:b;
前面提到了,#define就相当于编辑器里的ctrl+h替换功能,如果这个类似于两者比较取大者函数的Max被应用于这样的表达式中:if(50+Max(10,50) >= 100),你想要的结果可能是10与50之中取较大者加上50跟100比较大小,即50+50 >= 100 为true,但实际上编译器会这样解释50 + 10 >50 ? 10:50, 最终50+Max(10,50)会返回10 >= 100 为false。
宏定义在替换的时候经常要考虑运算符的优先级,无论何时你在写类似Max这种宏同时又想让它正确工作,请对所有实参加一个小括号。而且宏定义因为是在编译之前被处理,它是不参与翻译过程的语法检查的。
但实际上我们完全可以避免这种宏的使用。书中提到了用template inline函数来实现,同时拥有宏带来的效率以及一般函数的所有可预料行为和类型安全性。
对于常量,用const、enum替换#define。
对于类似函数的宏,用inline函数替换#define
条款3:Use const whenever possible
译:尽可能使用const
const是C++一个多才多艺的关键字。它允许我们指定一个语义约束(也就是指定一个在任何情况“不该被改动”的对象)。
我们可以在变量,函数,成员变量,指针等等地方加上const修饰,使其被标明为不可修改。
值得一提的是关于指针声明为const,可以是把指针声明为常量,亦可以是把指针所指物(指针内容)声明
书中这段原话我觉得很不错:如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
此外,还提到了bitwise constness和logical constness.其中提到编译器认为的bitwise const实际上还是可以被修改,书中例子是用一个指针指向const成员函数返回的地址,然后修改了该指针内容。
然后是logical constness,const成员函数可以修改它所处理对象内的某些变量。书中提到用mutable(可变的)关键字释放 掉non-static成员变量的bitwise constness约束。这种情况我倒是遇到过,因为需要在const函数中对检查状态的修改,不过我当时用强制转型解决了,也不知道当时做的是否正确。
最后就是const和non-const成员函数避免重复了,当const和non-const成员函数的内容基本一致,为了避免代码重复,应该只实现一次并重复使用它,即令其中一个调用另一个。
然后要进行常量性转除(casting away constness),而转型不是个好的方法(条款27),正确的做法是令non-const方法调用const方法避免代码重复,这过程需要一个转型动作
嗯,本来想用书中代码,还是自己写个实例吧。
#include <iostream> class A { public: enum {arySize = 10}; A() :m_text("123456789") { } ~A() { } const char& operator[](std::size_t pos) const { printf("const function\n"); return m_text[pos]; } char& operator[](std::size_t pos) { printf("non-const function\n"); return const_cast<char&>(static_cast<const A&>(*this)[pos]); } private: std::string m_text; }; int main() { A a; const A b = a; b[0];//输出const function a[0];//输出non-const function\n const function return 0; }
这就是一次实现,重复调用,避免代码的重复。看起来好像只是省了一句代码,但实际上若在返回前还做一堆大处理,什么边界检查、状态检查的,这样实现就会达到我们的目的:代码复用。
const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。声明为const可以帮助编译器检测错误,减少调试查错的时间成本。
当const和non-const成员函数有着实质等价的实现时,令non-const版本偏用const版本可避免代码重复,反之不可行。