写在前面
读了读《Effective C++》甚至感觉这本书就是专门给我这种弟弟写的,本文也是此书的阅读笔记
正文
一、习惯C++
作为一个写了两年C#和Shader被商业引擎惯坏了的程序员,看见老朋友甚至有点陌生
01:把C++当做一个语言联邦
把C++当做由四个次语言组成的联邦,从一个次语言到另一个次语言时,守则可能改变。
这四个次语言分别是:C、Object-Oriented C++、Template C++、STL
02:尽量以const、enum、inline 替换 #define
#define ASPECT_RATIO 255
开发者极有可能被它所带来的编译错误感到困惑,编译器可能提到255而不是ASPECT_RATIO,也许该语句被其他预处理干掉,追踪它会浪费时间。
解决办法就是用常量替换宏
const int AspectRatio = 255;
着重说明
-
由于常量经常定义于头文件内,因此有必要将指针(而不是指针所指之物)声明为const
const char* const authorName = "TOSHIRON";
-
对于Class专属常量,为了确保他只有一份实体,必须使其成为static成员
class GamePlayer { private: static const int NumTurns = 5; ... }
-
如果不想让别人获取到指向某个常量的指针,因为取const地址是合法的,所以可以用enum取代
class GamePlayer { private: enum{ NumTurns = 5 }; ... }
-
用内联函数替代宏,以获得相同的效率和功能
#define 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); }
03: 尽可能使用 const
const出现在*左边,被指物是常量
const出现在*右边,指针自身是常量
const出现在*两边,指针和所指事物都是常量
着重说明
-
令一个函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性
我感觉这几乎主要是为了单独避免一种情况class Rational{ ...}; const Rational operator* (const Rational& lhs, const Rational& rhs); ... Rational a,b,c; ... if(a*b = c){ ...}
找错麻烦?因为常量不允许赋值所以会直接报错
-
利用常量性(constness)不同,重载函数
class TextBlock { public: ... const char& operator[] (std::size_t position) const //第二个const是其重载的依据 { return text[positon]; } char& operator[] (std::size_t position) { return text[position]; } private: std::string text; }
TextBlock tb("Hello"); std::cout << tb[0]; //调用non-const Const TextBlock ctb("World"); std::cout << ctb[0]; //调用const
-
const成员函数不可以更改对象内任何non-static成员变量;解决办法就是用 mutable 关键字修饰,使变量总是可更改的,及时在const成员函数内。
-
在 const 和 non-const 成员函数中避免重复,常量性重载往往伴随着大量重复代码,这时,我们需要让non-const 利用 const 函数。
class TextBlock { public: ... const char& operator[] (std::size_t position) const { ... ... return text[positon]; } char& operator[] (std::size_t position) { return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]); } private: std::string text; }
首先,将 *this 转型为 const,调用const成员函数,再移除const
04: 确定对象在使用前已经初始化
这是当然了,至少我使用过的任何编程语言都有要求这一点
int x;
x有可能被初始化为0
class Point
{
int x, y;
};
...
Point p;
p的成员变量有时候会初始化为0,有时候不会,所以手动初始化很有必要。
着重说明
-
C++规定,对象的成员变量初始化动作发生在进入构造函数本体之前。
class PhoneNumber{ ...}; class ABEntry { public: ABEntry(const string& name, const string& address, const list<PhoneNumber>& phones); private: string theName; string theAddress; list<PhoneNumber> thePhones; int numTimeConsulted; ABEntry(const string& name, const string& address, const list<PhoneNumber>& phones) { theName = name; theAddress = address; thePhones = phones; numTimesConsulted = 0; }
书上的说法是 构造函数中那四行四赋值,而不是初始化
构造函数应该改为ABEntry(const string& name, const string& address, const list<PhoneNumber>& phones) :theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) { }
-
为解决两个不同编译单元内的初始化次序问题,使用local static 替代 non-local
class FileSystem { public: ... size_t numDisks() const; ... }; extern FileSystem tfs;
替代为
class FileSystem { public: ... size_t numDisks() const; ... }; FileSystem& tfs() { static FileSystem fs; return fs; }
这样,在调用时才不用在乎初始化顺序的问题
二、构造、析构、赋值运算
因为GC的存在,很长时间没有用析构函数了
05: 了解C++默默编写并调用哪些函数
编译器可以暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符,以及析构函数。
class Empty{
};
相当于
class Empty
{
public:
Empty(){
...}
Empty(const Empty& rhs){
...}
~Empty(){
...}
Empty& operator = (const Empty& rhs){
...}
};
着重说明
- 当对一个“内含reference 成员”或“内含const 成员”进行赋值操作时,编译器自己生成的赋值重写无法完成此工作,需要自己专门写。
- 如果把赋值重写设为private,那更调用不了。
06: 如果不想用编译器自动生成的函数,就应该明确拒绝
如果想要驳回编译器自动提供的函数,可以将成员函数声明为 private 并且不与实现。
class Uncopyable
{
protected:
Uncopyable(){
}
~Uncopyable(){
}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator = (const Uncopyable&);
};
然后之后的类可以继承Uncopyable,反正C++可以多继承,但是多继承会阻止empty base class optimization,慎重
class Abc:private Uncopyable{
...}
07: 为多态基类生命 virtual 析构函数
class TimeKeeper
{
public:
TimeKeeper();
virtual ~TimeKeeper()