概述

花时间通读了下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,那么在定义构造函数时,最好这样去初始化

cA:A(const string& name,const string& address,
const list<PhoneNumber>& 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。

cclass 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<Widget> pw, int priority);

//现在调用processWidget
processWidget(new Widget, priority());

现在你会发现这个代码无法通过编译,因为tr1::shared_ptr需要一个原始指针,但他的构造函数是个explict构造函数。

现在你把他修改成:

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());

但是,这样会有很大可能导致内存泄漏。
思考,当我们对priority的调用失败时,我们无法阻止内存泄漏的产生!
避免方案其实很简单,就如下,用一个独立语句拆分他。

std::tr1::shared_ptr<Widget> 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对象的创造过程)

(未完待续,最近实习入职,有时间继续写)


wind3110991
603 声望10 粉丝

菜鸟程序员一个 = 。-