Effective C++ 笔记
1、尽量以const,enum,inline替换#define
对于变量, const 更容易定位问题, const 可能会使代码交少。
相比 const char* const author = "Scott"; const std::string author("Scott"); 更好一些。
宏函数更是难以理解,最好用template inline 代替。
2、尽可能使用const
char greeting[] = "Hello"; char* p = greeting; // non-const pointer, non-const data const char* p = greeting; // non-const pointer, const data char* const p =greeting; // const pointer, non-const data const char* const p = greeting; // const pointer, const data
const(修饰data) char* const(修饰指针) p = greeting; // 以char* 为分界, 左边的修饰对象,右边的修饰指针。
std::size_t length() const; //保证函数内部的常量性,内部变量不能进行赋值。(mutable可以使变量在const成员函数内被更改)
3、确定对象被使用前初始化
在构造函数的初始化列表中进行初始化,而不是在构造函数内进行赋值。
4、明确拒绝不想使用编译器自动生成的函数。
例如单例模式中,将 copy,copy assignment, operator= 定义成私有函数,并且只声明,不实现。
5、为多态基类声明virtual析构函数。
当derived class对象经由一个base class指针被删除,而base class带着一个non-virtual析构函数,其结果使未定义的--其结果通常是derived成分没有被销毁。
如果class不含virtual函数,通常表示它并不想作为case class, 当class不会被作为base class时,令其析构函数为virtual往往是个馊主意。
virtual函数会增加对象的大小。将所有析构函数都声明为virtual,就想从未声明它们一样,都是错误的。 只有当class内含至少一个virtual函数,才声明virtual析构函数。
pure virtual 函数,不能实例化的class,类似接口。 virtual ~AWOV()=0;
6、不要让异常逃离析构函数
例如关闭数据库的动作,close() ,可以将其close()的操作放到析构函数中,但是如果close()产生异常,我们就没有办法处理了。可以提供一个bool close()接口,这样客户就可以知道close()是否成功,而做出相应处理。
7、不要在构造和析构函数中调用virtual函数。
8、以对象管理资源
用智能指针管理内存,unique_ptr, shared_ptr。
使用STL 中的容器(vector)代替array等。
class Lock { public: Lock(Mutex *mutex):_mutex(mutex) { lock(_mutex); } ~Lock() { unlock(_mutex); } private: Mutex *_mutex; }
void test()
{
Mutex mutex;
{ //加锁部分
Lock(&mutex);
...
}
}
当复制自身时, Lock m1(&mutex); Lock m2(m1); 会发生什么?
我们可以进行完善, 1,禁止复制。 2,用智能指针管理_mutex;
9 、以独立语句将newed对象置于智能指针
processWidget(std::shared_ptr<Widget>(new Widget), priority());
上面的代码可能会引起内存泄漏,主要原因是,我们无法确定, new Widget, priority(), shared_ptr构造函数,它们的执行顺序。如果先new Widget, 在执行 priority(),而在执行 priority()时,发生异常,就会产生内存泄漏。
10、让接口容易被正确使用,不易被误用。
尽量使type的行为和内置type一致。
返回一个 shared_ptr而非原始指针,这样可以阻止客户犯下内存泄漏的问题。
shared_ptr 可以提供一个 deleter。(可以防止DLL问题,自动解锁互斥锁。)
11、以const 引用代替 值传递
值传递会增加构造函数调用。
引用对象还可以解决切割问题。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> using namespace std; class Base { public: virtual void Print(void) const { cout << "Base print." << endl; } }; class Derived : public Base { public: virtual void Print(void) const { cout << "Derived print" << endl; } }; void PrintClassByVale(Base value) { value.Print(); } void PrintClassByReference(const Base &value) { value.Print(); } int main(void) { Derived der; PrintClassByVale(der); PrintClassByReference(der); return 0; }
对于内置类型,通常值传递更合适。
12、不可以返回局部对象的指针和引用。
13、尽量延后变量的定义。
对于一个变量是定义在for循环外还是循环内有以下考虑:
A:在 for 外部, 1个构造+一个析构+N个赋值。
B:在 for 内部, N个构造+N个析构。
如果 class 的一个赋值操作成本低于构造+析构, A的效率高于B,尤其时N比价大时。但是A的作用域更大,对程序的易维护性造成冲突。除非确认赋值成本比构造+析构的成本低并且代码对执行效率高度敏感,否则应该使用方案B。
14、尽量减少转型动作,使用新式转型。
const_cast<T>(expression), 通常用来将对象的常量性转除。
dynamic_cast<T>(expression),通常执行“安全向下转型”,也就是用来决定某个对象是否归属继承体系中的某个类型。(可能消耗重大运行成本,里面调用的类似strcmp的函数)
reinterpret_cast<T>(expression),通常用于低级转型。
static_cast<T>(expression),强迫隐式转换,non-const 转 const, int 转 double, void* 转 type指针, pointer-to-base 转 pointer-to-derived等。(无法将const转non-const)
一种场景:
class A
{
public:
void Print(void) { cout << "print A" << endl; } }; class AB : public A {
public:
void Print(void) { cout << "print AB" << endl; }
};
class AC : public A
{
public:
void Print(void) { cout << "print AC" << endl; }
};
class AD : public A
{
public:
void Print(void) { cout << "print AD" << endl; }
};
void test(A *a) {
if (nullptr == a) {
return;
}
AB *ab = dynamic_cast<AB>(a);
if (nullptr == ab) {
return;
}
}
我们想在一个容器中放 AB,AC,AD时,我们可以放 A的指针。 当我们把对象取出来时,再使用 dynamic_cast 进行转换。但是由于dynamic_cast会导致效率下降。我们考虑通过 virtual 来避免进行类型转换。
15、避免返回handles指向对象内部成分。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <memory> using namespace std; class Point { public: Point(){} Point(int x, int y) : _x(x), _y(y) {} void set_x(int x) {_x=x;} void set_y(int y) {_y=y;} private: int _x; int _y; }; struct RectData { Point ulhc; Point lrhc; }; class Rectangle { public: Rectangle() {_data = new RectData();} ~Rectangle() {delete _data;} Point &UpperLeft() const {return _data->ulhc;} Point &LowerRight() const {return _data->lrhc;} private: RectData *_data; }; int main(void) { const Rectangle rec; rec.LowerRight().set_x(5); return 0; }
rec 是 const 对象,本应该是不可改变的,但是上面的代码确可以编译。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
const Point &UpperLeft() const {return _data->ulhc;} const Point &LowerRight() const {return _data->lrhc;}
在返回类型加上 const 可解决上面的问题。
避免返回handles(包括references,指针,迭代器)指向对象内部,遵守这条规则,可增加封装性,帮助const成员函数的行为像个const,并且将客户拿到无效引用,指针的可能性将到最低(如果返回一个引用,指针,就要确保对象的生存时间比客户生存时间长。)