最近开始看c++经典著作《effective c++》,总结了一些要点。
关键字的使用
1 自定义类的构造函数应该加上explicit,目的是为了防止隐式转换。除非有好的理由说明需要隐式转换,否则默认加上explict防止出现没有预料到的情况。
2 对于内置类型(int, double)和STL来说,pass-by-value比pass-by-reference更加高效,但是对于自定义的类,pass-by-reference比pass-by-value更加高效。
3 由于#define定义的宏常量可能没有进入到记号表,导致追踪不到。我是从来没遇到过这种情况,但是作者还是建议在众多场合不要使用#define。
- 定义常量整数或实数时,尽量用const int或const double代替。这会导致更少量的码。
- 定义常量指针时,要用两次const。const char* authorname = “scott meyers”。
- 定义类中的常量时,还要用static修饰。
- 定义类中的数组时,必须要定义常量,这时如果编译器不允许在类内声明时定义常数,就可以用enum代替。例如
class GamePlayer{
private:
enum { NumTurns = 5};
int scores[NumTurns];
}
enum的行为比较像#define而不像const。例如取一个enum的地址是不合法的,而取一个#define的地址通常也不合法。
- 用template inline函数代替#define函数
错误用法:#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
正确用法:template<typename T>
inline void callwithmax(const T&a, const T& b)
{
f(a > b ? a : b);
}
4 多用const关键字可帮助编译器侦测出错误用法。const语法虽然变化多端,但并不莫测高深。如果关键字const出现在星号左边,表示被指物是常量。如果const出现在星号右边,表示指针自身是常量。如果出现在星号两边,表示被指物和指针两者都是常量。
- const用于返回值,避免返回值被赋值。
class Rational {...};
const Rational operator*(const Rational& a, const Rational & b); 此处之所以要返回一个const对象是为了防止给返回值赋值这样的暴行,比如if( (a*b)=c)这样无意识的错误。
这样做可以保持与内置类型兼容。如果a和b都是内置类型,这样的代码直截了当就是不合法。因此自定义类型最好也保持这样。
- const用于成员函数,两个成员函数若只是常量属性不同,可以被重载(也就是说是两个不同的函数)。const成员函数只能更改const成员,而不能更改non-static成员变量。
如果要让const成员函数修改部分成员变量,可以使用mutable关键字来解禁。
为了避免const和non-const成员函数中代码重复,可以在non-const成员函数中调用const函数,反之不可。
class TextBlock{
public:
const char& operator[](size_t position) const
{
...
return text[position];
}
char & operator[](size_t position)
{
return const_cast(static_cast<const& TextBlock>(*this)[position]);
}
5 确定对象在调用之前已经被初始化了。
如果遇到一个全局对象A调用另一个全局对象B时,必须要保证B在A之前先被初始化了。
为了避免可能会忘记先后顺序的操作,可以将全局对象放进函数内(该对象在此函数内声明为static),并在函数内返回一个reference指向它所含的对象。
class FileSystem {...}
FileSystem& tfs()
{
static FileSystem fs; //定义并初始化fs
return fs;
}
class Directory {..};
Directory::Directory(params)
{
std::size_t disks = tfs().numDisks(); //调用FileSystem类
}
Directory & tempDir()
{
static Directory td; //定义并初始化对象
return td;
}
构造/析构/赋值
6 编译器会自动生成构造、析构、赋值等函数,但如果用户自定义了构造函数,则会覆盖原来的默认构造函数。
7 为了禁止拷贝等行为,可以将成员函数声明为private并不予实现。
8 任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。如果class不含virtual函数,通常表示它并不意图被用做一个base class。当class不企图被当作base class,令其析构函数为virtual往往是个馊主意。
9 不建议继承标准容器或者带有非virtual析构函数的class。比如class SpecialString:public string是不建议的。因为string类内部不含有virtual函数,可能会导致未定义行为。
10 不要在析构函数中吐出异常。如果析构函数中需要调用可能抛出异常的函数,析构函数应该捕捉任何异常,然后吞下他们从而不让异常传播。如果客户需要对某个函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
11 在构造函数和析构函数中,都不应调用virtual函数。
12 当为派生类写拷贝构造函数或等号操作符时,要记得把基类也一并拷贝。
参考:《effective c++》