概述
花时间通读了下Meyers大神的著作《Effective c++》,其中受益很多,毕竟书中有很多东西是之前在写代码时完全没有考虑过的地方,作者用独到的眼光来告诉我们,c++不是一门简单的编程语言,而是一门堆积埃菲尔铁塔式程序的艺术。
接下来,让我对书中的“条款”用自己的语言方式,作一些总结和个人心得批注。
(PS:其中带“*”号的条款是本人不太理解或者令人可能较难理解的条款。对其中一些难以理解的条款我会给予详细解释,简单条款将略过)
Part1: 在写c++时,“让自己习惯c++”:
1.c++可以分为:(1)C (2)Object-Oriented C++ (3)Template C++ (4)STL。
2.尽量用const,enum,inline代替#define,或者说,宁可用编译器代替预处理器。因为宏定义容易出错(思考define函数进行运算时需要加上小括号)。
3.尽可能用const定义常量。
4.确定对象在使用前已先被初始化。特别对于构造函数,最好用成员初值列(member initialization list),而不是在构造函数内使用赋值。
举个例子:你有一个类A,那么在定义构造函数时,最好这样去初始化
c
A:A(const string& name,const string& address, const list
& phones): theName(name), thePhones(phones), numTimesConsulted(0){}
这样的话,你无需对构造函数内部本身进行任何动作。理由在于,对大多数类型而言,这样比起调用默认构造函数高效许多。
Part2: 构造/析构/赋值运算
5.了解c++默默编写了和调用了哪些函数。
就是比如说说你要清楚,c++在编译时会拒绝哪些赋值动作,拒绝哪些?对于一个class,编译器会默认为类创建default构造函数、析构函数、copy构造函数、copy assignment操作符。
6.若不想用编译器自动生成的函数,就拒绝他。比如,你可以把一个类的复制构造函数放在private里,在子类继承他时,使用私有继承,让类uncopyable。
7.为多态基类声明virtual析构函数。
对于一个多态基类而言,应该对他声明一个virtual析构函数,就是说,假如一个类带有任何的virtual函数,我们就应该让他拥有一个virtual析构函数。
8.不要让析构函数吐出异常。如果有必要,那么在class中提供一个普通函数执行该操作。
9.绝对不要在构造和析构函数中调用virtual函数。
*10.令operatior=返回一个reference to *this。
c
class Widget{ ...... Widget& operator+=(const Widget& rhs) //返回类型是个reference,指向当前对象 { ... return *this; } ...... }
11.在operator=中处理“自我赋值”。你不能保证用户不会让对象做自我赋值这种看起来虽然愚蠢的事情。
*12.确保复制对象时没有忘记他的每一个成员。包括所有的公有与私有成员。
Part3: 资源管理
13.用对象来管理资源。
这里推崇一个概念--RAII(Resource Acquisition Is Initialization),你在获得获得一个对象时,必须对他进行相应有效的管理,使用STL提供的auto_ptr或者shared_ptr能让你更加轻松使用对象。推荐使用shared_ptr,无须担心复制动作带来的麻烦。
*14.小心资源管理的copy行为。
--对RAII对象做到禁止复制。
--对底层资源使用引用计数法(reference-count),比如在写锁操作时。
--对复制操作进行深拷贝(考虑堆的深复制)。
--转移底层资源的所有权。当一个对象被复制,资源的拥有权将从被复制的对象转移到目标对象上。
*15.在资源管理类中提供对原始资源的访问。
16.new与delete时采用相同形式。
S *s1 = new S;
delete S;
S *s2 = new S[100];
delete []s2;
*17.以独立语句将newed对象储存于智能指针中。
int priority();
void processWidget(std::tr1::shared_ptr pw, int priority);
//现在调用processWidget
processWidget(new Widget, priority());
现在你会发现这个代码无法通过编译,因为tr1::shared_ptr需要一个原始指针,但他的构造函数是个explict构造函数。
现在你把他修改成:
processWidget(std::tr1::shared_ptr(new Widget), priority());
但是,这样会有很大可能导致内存泄漏。
思考,当我们对priority的调用失败时,我们无法阻止内存泄漏的产生!
避免方案其实很简单,就如下,用一个独立语句拆分他。
std::tr1::shared_ptr pw(new Widget);
processWidget(pw, priority());
Part4: 设计与声明
18.让接口容易被正确使用,不易被误用。
*19.设计class犹如设计type。
20.宁用pass-by-reference-to-const代替pass-by-value
class Person(){
public:
Person();
virtual ~Person();
...
private:
string name;
string address;
};
class Student():public Person{
public:
Student();
~Student();
...
private:
string schoolName;
string schoolAddress;
};
bool validateStudent(Student s);
Student plato;
bool platoIsOk = validateStudent(plato);
当你用这个方法去传递一个Student对象时,总体成本是六次构造函数和六次析构函数。(自己算下string对象和student对象的创造过程)
(未完待续,最近实习入职,有时间继续写)