-
尽量使用const、enum、inline替换#define (对于单纯常量最好以const对象或enums替换#define)
• 无法利用#define创建一个class专属常量,因为#define并不重视作用于,意味着#define不仅不能用来定义class专属常量,也不能够提供任何封装性 -
尽可能使用const
• 用于class外部修饰global或namespace作用域中的常量,或修饰文件、函数、或区块作用域被声明为static的对象
• const出现在星号左边表示被指物是常量,出现在星号右边表示指针自身是常量
• 两个成员函数如果只是常量不同
• 一个更改了“指针所指物“的成员函数虽然不能算是const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为以bitwise constness(私有成员赋值)不会引发编译器异议
• 使用mutable可以释放掉非静态成员变量的赋值 -
确定对象被使用前已先被初始化
• 永远在使用对象之前先将他初始化。对于无任何成员的内置类型,你必须手工完成此事(确保每一个构造函数都将对象的每一个成员初始化)
• 总是使用成员初值列(比赋值效率更高)
• 在成员变量的初值系由文件或数据库读入时将那些赋值操作移往某个函数(通常是private)供构造函数调用
• 在多线程赋值启动阶段手工调用所有引用返回函数,消除与初始化有关的竞争
• 在跨编译单元初始化次序上使用本地静态对象替换非本地静态对象 -
编译器可以自动为class创建默认构造函数、拷贝构造函数、拷贝赋值操作符以及析构函数
-
为了不让编译器自动创建相关函数可以将相应的成员函数声明为private并且不予以实现
class Uncopyable {
protected:
Uncopyable(){}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
}; -
为多态基类声明virtual析构函数
• 带多态性质的基类应该声明一个virtual析构函数,如果class带有任何virtual函数,他就应该有一个virtual析构函数
• classes的设计目的如果不是作为基类使用或不是为了具备多态性,就不该声明virtual析构函数 -
不要让异常函数逃离析构函数
• 析构函数对异常做出的反应应该在其他函数中操作
class DBConn {
public:
DBConn(){}
void close() {
db.close();
closed = true;
}
~DBConn() {
if (!closed) {
try
{
db.close();
}
catch (const std::exception&)
{
//制作运转记录,记下对close的调用失败(如果关闭失败记录下来并结束程序或吞下异常)
}
}
}
private:
Dbconnection db;
bool closed;
}; -
绝不在构造和析构过程中调用virtual函数
• 构造函数被调用时不会调用派生类函数而会调用基类虚函数(在基类构造期间virtual函数不是virtual)
• 无法使用virtual函数从基类向下调用 -
令operator=返回一个*指向this引用
class Widget {
public:
Widget& operator=(const Widget& rhs){
return *this
}
}; -
在operator=中处理自我赋值
• 确定任何函数如果操作一个以上的对象而其中多个对象是同一个对象时,其行为仍然正确
• 对operator=赋值确保做到来源对象和目标函数地址、语句顺序以及拷贝和数据交换正确
class Bitmap{};
class Widget {
private:
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs) {
if (this == &rhs) {
return *this;
}
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
//证同测试
Widget& Widget::operator=(const Widget& rhs) {
if (this == &rhs) {
return *this;
}
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
class Widget {
void swap(Widget& rhs);
};
//数据交换
Widget& Widget::operator=(const Widget& rhs) {
Widget temp(rhs);
swap(temp);
return *this;
} -
拷贝函数应该确保复制对象内所有成员变量以及所有基类成分,共同调用基类和派生类成员赋值。
-
资源管理1
• 把资源放进对象内,依靠C++的析构函数自动调用机制确保资源被释放(一旦资源被销毁,其析构函数自动被调用,于是资源被释放)
• 智能指针:若通过拷贝构造函数或拷贝赋值操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权 -
资源管理2
• 对某些类实施禁止复制,将拷贝操作声明为private
• 利用计数器办法当计数器为0时对智能指针调用删除器 -
一般显式转换比较安全,而隐式转换对客户较方便
-
在new表达式中使用[]必须在相应的delete表达式中也使用[]。如果在new表达式中不使用[],一定不要在相应的delete表达式中使用[](最好不要对数组形式使用typedefs)
-
将newd对象存储于智能指针内,因为当抛出异常时,普通对象难以察觉资源泄露(auto_ptr、tr1::shared_ptr)
-
用const引用传递替换值传递
• 利用const解决多次构造和析构
• bool validateStudent(const Student& s);
• 利用const引用传递避免参数被拆分
• void printNameAndDisplay(const Window& w) {
std::cout << w.name();
w.display();
}
• 值传递往往比引用传递效率高 -
const定义时就初始化,不能改变(const阻止变量改变)
-
将成员变量声明为private
• 成员变量应该是private
• protected成员变量和public成员变量一样缺乏封装性(如果成员变量改变会造成不可预知的代码破坏)
• private提供封装而其它不提供封装 -
成员函数带来的封装性比非成员函数的低(利用非成员、非友元函数替换成员函数可以增加封装性)
-
需要为某个函数的所有参数进行类型转换时,那么这个函数必须是非成员函数
-
利用成员swap函数时提供一个非成员swap函数调用(成员swap函数不可抛出异常)
-
尽量直到非得使用该变量的前一刻为止才定义变量,避免构造析构非必要的对象,避免无意义的default的构造行为
-
尽量少做转型动作
• const_cast:通常用来将对象的常量性转除
• dynamic_cast:用来决定某对象是否归属继承体系中的某个类型
• reinterpret_cast:执行低级转型
• static_cast:强迫隐式转换
• 宁可使用新式转型不要使用旧式转型
• 如果转型是必要的,将其放在某个函数背后 -
异常处理
• 异常安全函数即使发生异常也不会泄露资源或允许任何数据结构被破坏
• struct PMImpl
{
std::tr1::shared_ptr bgImage;
int imageChange;
};
class PreetyMenu {
private:
Mutex mutex;
std::tr1::shared_ptr PImpl;
};
void PreetyMenu::changeBackgroup(std::istream& imgSrc) {
using std::swap;
Lock ml(&mutex); //获得mutex的副本数据
std::tr1::shared_ptr pNew(new PMImpl(*pImpl));
pNew->bgImage.reset(new Image(imgSrc)); //修改副本
++pNew->imageChanges;
swap(pImpl, pNew); //置换数据
} -
将文件间的编译依存关系降至最低
• 为声明式和定义式提供不同的头文件
• 利用接口实现降低耦合 -
public继承以为着is-a。适用于基类身上的每一件事情一定也适用于派生类上
-
避免遮掩继承而来的名称
• 利用using声明式或转交函数避免派生类掩盖基类函数
• class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
};
class Derived :public Base {
public:
using Base::mf1;
using Base::mf3;
virtual void mf1();
void mf3();
void mf4();
}; -
区分接口继承和实现继承
• 声明一个虚函数的目的是为了让派生类只继承函数接口
• 声明非纯虚函数的目的是让派生类继承该函数的接口和缺省实现
• 声明非虚函数的目的是为了令派生类继承函数的接口及一份强制性实现 -
绝不重新定义继承来的非虚函数
• 任何情况下都不要重新定义继承而来的非虚函数 -
绝不重新定义继承来的缺省参数值
• 派生类继承基类缺省参数时会调用基类的缺省参数函数,缺省参数值都是动态绑定,而虚函数唯一覆盖写的东西是动态绑定 -
private继承意味着根据某物实现出,通常比复合的级别低,private继承可以使空基类空间最优化
-
virtual继承会增加大小、速度、初始化、赋值复杂度等成本(解决办法是虚基类不带任何数据)
-
声明template参数时前缀关键字class和typename可互换,使用typename标识嵌套类型名称但不得在基类列或成员初值列内以它作为基类修饰符
-
成员函数模板生成可接受所有兼容类型,当声明成员模板用于拷贝构造或泛化赋值操作还得声明正常的拷贝构造函数和拷贝赋值操作符
Effective C++
最新推荐文章于 2025-05-20 18:02:11 发布