Effective C++
一、让自己习惯C++
1.1 视C++为一个语言联邦
- C:区块、语句、预处理器、内置数据类型、数组、指针
- Object-Oriented C++:类(构造和析构函数)、封装、继承、多态、virtual函数
- Template C++:泛型编程
- STL:容器、迭代器、算法、函数对象
1.2 尽量用const、enum、inline替换#define
第一种:
//头文件
static const int NumTurns = 5; //其实是声明
//实现文件
const int GamePlayer::NumTurns; //定义,头文件声明了,这里只能定义,不能赋值
第二种:
class CostEstimate{
private:
static const double FudgeFactor; //static class 常量声明
... //位于头文件中
};
const double //static class 常量定义
CostEstimate::FudgeFactor = 1.35; //位于实现文件内
第三种:
class GamePlayer{
private:
enum { NumTurns = 5 }; //the enum hack == #define
int scores[NumTurns];
...
};
Note:
- 对于变量宏:const、enum 代替 #define
- 对于形似函数宏:inline 代替 #define
1.3 尽可能用const
类型是常量
void f1 (const Widget* pw);
void f2 (Widget const * pw) //两者一样
//第一个const 指针指向的数据为常量
//第二个const 指针本身为常量
const char * const p = "Hello"
//改变const函数里面的变量可变
mutable
1.4 确定对象被使用前已先被初始化
int x = 0; //对int进行手工初始化
const char* text = "Hello"; //对指针进行手工初始化
double d;
std::cin >> d; //以读取input stream的方式完成初始化
确定初始化而非赋值
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
theName = name; //这些都是赋值,而非初始化
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
以下是初始化
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
:theName(name),
theAddress(address),
thePhones(phone),
theTimesConsulted(0) //现在这些都是初始化
{} //现在,构造函数本体不必有任何动作
Note
- 内置型对象手工初始化。
- 构造函数最好使用成员初值列而不要在构造函数本体内使用赋值操作。其值列列出的成员变量排列次序与class声明一致。
- 避免“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。
二、构造/析构/赋值运算
2.1 了解C++默默编写并调用哪些函数
class Empty{
public:
Empty() {...} //default构造函数
Empty(const Empty& rhs) {...} //copy构造函数
~Empty() {...} //析构函数,有可能为virtual
Empty& operator=(const Empty& rhs) {...} //copy assignment操作符
}
Note
- 编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符以及析构函数。
2.2 若不想使用编译器自动生成的函数,就该明确拒绝
拒绝拷贝
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); //企图拷贝h1,不该通过编译
h1 = h2; //企图拷贝h2,不该通过编译
2.3 为多态基类声明virtual析构函数
若base class带有non-virtual析构函数,可能造成基类析构了,派生类未销毁,造成资源泄露、败坏数据结构。
Note
- polymorphic base classes应该声明一个virtual析构函数,
- class不为base class,不该声明virtual 析构函数。
2.4 别让异常逃离析构函数
在析构函数中写好异常处理
class DBConn(){
public:
...
void close()
{
db.close();
closed = true;
}//供用户调用的新函数
~DBConn()
{
if (!closed) {
try {
db.close(); //关闭连接,如果用户不这么做
}
catch (...) {
//制作运转记录,记下对close的调用失败
}//如果关闭动作失败,记录下来关闭程序或吞下异常
}
}
private:
DBConnection db;
bool closed;
}
2.5 绝不在构造和析构过程中调用virtual函数
2.6 令operator=返回一个reference to *this
class Widget{
public:
...
Widget& operator=(const Widget& rhs) //返回类型是个reference,指向当前对象
{
...
return* this; //返回左侧对象
}
Widget& operator+=(const Widget& rhs)
{
...
return *this;
}
...
};
Note
- 令赋值(assignment)操作符返回一个reference to *this
2.7 在operator=中处理“自我赋值”
自我赋值安全性
避免自我赋值,首先进行证同操作。
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this; //证同测试,如果是自我赋值,就不做任何事
delete pd;
pb = new Bitmap(*rhs.pd);
return *this;
}
异常安全性
class Widget {
...
void swap(Widget& rhs); //交换*this和rhs的数据
...
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); //为rhs数据制作一份副本
swap(temp); //将*this数据和上述复件的数据交换
return *this;
}
2.8 复制对象时勿忘其每一个成分
Note
- Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”;
- 不要尝试某个copying函数实现另一个copying函数。应该将共同机能放进度三个函数,并由两个coping函数共同调用。
三、资源管理
资源
文件描述器、互斥锁、图像界面中的字型和笔刷、数据库连接、网络sockets