Effective C++总结

来源:互联网 发布:女神的标准知乎 编辑:程序博客网 时间:2024/04/30 01:01

条款1:View C++ as a federation of languages。视C++为一个语言联邦。分为四个部分:C、Object-Oriented C++(Class)、Template C++、STL


条款2:Prefer consts, enums, inlines to #defines。尽量以const, enum, inline替代#define。记住:

1.对于单纯常量,最好以const对象或enum替换#define

  2.对于形似函数的宏(macros),最好改用inline函数替换#define

C语言用#define可以做到两点,
第一,定义常量,如#define PI 3.1415926或#define NAME "Jade";
第二,定义宏表达式(函数),如#define min(a,b) (a)<(b)?(a):(b)。
但是这样做会有些问题,首先,不易调试,因为这个宏在预编译时就被替换为实际代码了,跟踪器一般都很难跟踪;其次,使用宏表达式,虽然样子像函数,但是由于会被替换为实际代码,行为又和函数不同,容易造成错误。
因此,建议使用const变量和inline函数替换宏的这两个功能。
 
const double PI = 3.1415926;
const char NAME[] = "jade";
inline min(a,b) { if (a < b) return a; else return b; }
 
class A
{
public:
    static const int N;
};
 
const int A::N = 100;


条款3:Use const whenever possible。尽可能使用const。记住:

1. 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。

      2. 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。

      3. 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。


条款4:Make sure that objects are initialized before they're used。确定对象被使用前已先被初始化。

为了避免在对象初始化之前过早地使用它们,你需要做三件事:

第一,手工初始化内置型non-member对象。

第二,使用成员初值列(member initializations lists)对会对象的所有成分。

最后,在“初始化次序不确定性”(这对不同编译单元所定义的non-local static对象是一种折磨)氛围下加强你的设计。

记住:

1. 为内置型对象进行手工初始化,因为C++不保证初始化它们。

      2. 构造函数最好使用成员初值(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。

      3. 为免除“跨编译单元之初始化次序”成员,请以local static对象替代non-local static对象。


条款5:Know what functions C++ silently writes and calls。了解C++默默编写并调用哪些函数

        请记住:编译器可以暗自为class创建default构造函数、copy构造函数、copy assign操作符,以及析构函数


条款6:Explicitly disallow the use of compiler-generated functions you do not want。若不想使用编译器自动生成的函数,就该明确拒绝。

      记住:为驳回编译器自动(暗自)提供的机能,可将相应的成员声明为private并且不予实现。使用像uncopyable这样的base class也是一种做法。

class Uncopyable

{

protected:

Uncopyable() {}                // 允许derived对象构造和析构

~ Uncopyable(){}

private:

Uncopyable(const Uncopyable&); //阻止copying

Uncopyable& operator=(const Uncopyable&);

};

class derived: public Uncopyable

{

};


条款7:Declare destructors virtual in polymorphic base classes.为多态基类声明virtual析构函数

        C++明确指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义---实际执行时通常发生的是对象的derived成分没有被销毁。

请记住:

1. polymorphic(带多态性质)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。

        2. classes 的设计目的如果不是作为base class使用,或不是为了具备多态性(polymorphically),就不该声明virtual析构函数。


条款8:Prevent exceptions from leaving destructors.别让异常逃离析构函数

       请记住:

1. 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉异常,然后吞下它们(不传播)或结束程序。

        2. 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。


条款9:Never call virtual functions during construction or destruction.绝不在构造和析构过程中调用virtual函数

        请记住:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。


条款10:Have assignment operator return a reference to *this.令operator=返回一个reference to *this

         请记住:

1. 令赋值(assignment)操作符返回一个reference to *this.

        2. 这个协议不仅适用于以上的标准赋值形式,也适用 于所有赋值相关运算:+=, -=, *= 。

因为你会写出这样的表达式:x=y=z=1;

注意:这只是一个协议,并无强制性。如果不遵循它,代码一样可通过编译。然而这份协议被所有内置类型和标准库提供的类型如string, vector共同遵守。因此,除非你有一个标新立异的好理由,不然还是随从吧。

Widget& operator= (const Widget& rhs)

{

       ….

     return *this;

}


条款11:Handle assignment to self in operator=.在operator=中处理“自我赋值”

         请记住:

1. 确保当前对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。

        2. 确定任何函数如果操作一个以上的,而其中中多个对象是同一个对象时,其行为仍然正确。

class Bitmap {…};

class Widget

{

private:

    Bitmap *pb;

}

实现方法一:

Widget& Widget::operator=(const Widget& rhs) // 一份不安全的operator=实现版本

{

delete pb;

pb = new Bitmap(*rhs.pb);

return *this;

}

问题:如果operator=函数内的*this(赋值的目的端)和rhs是同一个对象,delete pb就会删除对象。


实现方法二:

Widget& Widget::operator=(const Widget& rhs) //

{

Bitmap *pOrig = pb; //记住原先的pb

pb = new Bitmap(*rhs.pb);

delete pOrig;

return *this;

}


实现方法三:copy-and-swap

Widget& Widget::operator=(const Widget& rhs) //

{

Widget temp(rhs);

swap(temp); //定义一个void swap(Widget& rhs)交换*this和rhs的数据

return *this;

}


条款12:Copy all parts of an object.复制对象时勿忘其每一个成分

        请记住:

1. Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”

        2. 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。


条款13:Use objects to manage resources.以对象管理资源

        请记住:

1. 为防止资源泄漏,请使用RAII(Resource Acquisiton Is Initialization资源取得时机便是初始化时机)对象,它们在构造函数中获得资源并在析构函数中释放资源。

        2. 两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null.


条款14:Think carefully about copying behavior in resource-managing classes.在资源管理类中小心copying行为

        请记住:

1. 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。

       2. 普遍而常见的RAII class copying行为是:抵制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。


条款15:Provide access to raw resources in resource-managing classes.在资源管理类中提供对原始资源的访问

        请记住:

    1.APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理资源”的办法;

    2.对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。


条款16:Use the same form in corresponding uses of new and delete.成对使用new和delete时要采取相同形式

         请记住:

如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中全用[]。


条款17:Stored newed objects in smart pointers in standalone statements.以独立语句将newed对象置入智能指针

     以独立语句将newed对象存储于(置于)智能指针中。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。


条款18:Make interface easy to use correctly and hard to use incorrectly.让接口容易被正确使用,不易被误用

        请记住:

1. 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。

        2.“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。

        3.“阻止使用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。

        4. tr1::shared_ptr支持定制型删除器(custom deleter)。这可防范DLL问题,可被用来自动解除互斥锁(Mutexes)等等。


条款19:Treat class design as type design.设计class犹如设计type

         请记住:Class的设计就是type的设计。在定义一个新type之前,请确定你已经考虑过本条款覆盖所有讨论主题。


条款20:Prefer pass-by-reference-to-const to pass-by-value.宁以pass-by-reference-to-const替换pass-by-value

         请记住:

1. 尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)。

        2. 以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们面言,pass-by-value往往比较适当。


条款21: Don’t try to return a reference when you must return an object.必须返回对象时,别忘想返回其reference

        请记住:

绝对不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回 pointer或reference指向一个local static而有可能同时需要多个这样的对象。条款4已经为“在单线程环境中合理返回reference指向一个local static对象”提供了一份设计实例。


条款22:Declare data members private.将成员变量声明为private

        请记住:

1. 记住将成员变量声明为private。这可赋予客户访问数据一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分实现弹性。

        2. protected并不比public更具封装性。


条款23:Prefer non-member non-friend to member functions.宁以non-member、non-friend替换member函数

         请记住:宁可拿non-member non-friend函数替换member函数。这样做可以增加封装性、包裹弹性(packaging flexibility)和机能扩充性。


条款24:Declare non-member functions when type conversion should apply to all parameters.若所有参数皆需类型转换,请为此采用non-member函数

         只有当参数被列于参数列(parameter list)内,这个参数才是隐式类型转换的合格者。地位相当于“被调用之成员函数所隶属的那个对象”――即this对象――的那个隐喻参数,绝不是隐式转换的合格参与者。

请记住:如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。


条款25:Consider support for a non-throwing swap.考虑写出一个不抛异常的swap函数

当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常;

如果你提供一个member swap,也该提供一个non-member swap调用前者。对于classes(而非templates),请特化std::swap;

调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”;

为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。


条款26:Postpone variable definitions as long as possible.尽可能延后变量定义式的出现时间    

std::string encryptPassword(const std::string& password)

{

using namespace std;

string encrypted;

if (password.length() < MimmumPasswordLength)

{

    throw logic_error(“Password is too short”);

}

return encrypted;

}

分析:先定义变量encrypted(调用构造函数),如果if语句成立,则刚定义的encrypted没有使用。因此,应将string encrypted放在if语句后。

请记住:尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。


条款27: Minimize casting.尽量少做转型动作 
1.C++提供的四种形式转型
1).const_cast通常用来将对象的常量性转除。
2).dynamic_cast主要用来执行安全向下转型,也就是用来决定某对象是否归属继承体系中的某个型别。
3).reinterpret_cast意图执行低级转型,实际动作可能取决于编译器,不可移植。
4).static_cast用来强迫隐式转换。
2.dynamic_cast转换消耗成本,因此在注重效率的代码中对dynamic_cast保持机敏与猜疑。
3.用虚函数virtual 代替继承中出现的dynamic_cast


条款28: Avoid returning “handles” to object internal.避免返回handles指向对象内部成分

       请记住:

避免返回handles(包括references、指针、迭代器)指向对象内部。遵守这个条款可封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降至最低。


条款29: Strive for exception-safe code.为异常安全而努力是值得的 
1.异常安全的两个条件:当异常被抛出,带有异常安全的函数会:
1).不泄漏任何资源
2).不允许数据败坏
2.异常安全函数提供以下三个保证之一
1).基本承诺
2).强烈保证
3).不抛掷保证
3.有个一般化的设计策略很典型地会导致强烈保证,很值得熟悉它。这个策略被称为copy and swap。原则很简单:为你打算修改的对象作出一份读本,然后在那个副本上做一切必要的修改。若有任何修改动作抛出异常,元对象保持为改变状态。待所有改变成功后,再将修改过的那个副本和原对象在一个不排除异常的操作中置换。


条款30:Understand the ins and outs of inlining.透彻了解inlining的里里外外

         Template的具现化与inlining无关。如果你正在 一个template而你认为所有根据此template具现出来的函数都应该inlined,请将此template声明为inline。

程序为设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。换句话说如果f是程序库内的一个inline函数,客户将“f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须得新编译。这往往是大家不愿意见到的。然而如果f是non-inline函数,一旦它有任何修改,客户端只需重新连接就好,远比重新编译的负担少很多。如果程序库采取动态连接,升级版函数甚至可以不知不觉地被应用程序吸纳。

请记住:

1. 将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。

        2. 不要只因为function template出现在头文件,就将它们声明为inline。


条款31:Minimize compilations dependencies between files.将文件间的编译依存关系降至最低 
1.使用pimpl(pointer to implementation)来实现实现与接口的分离
2.如果使用object references 或 object pointers可以完成任务,就不要使用objects。
3.如果能够,尽量以class声明式替换class定义式
4.为声明式和定义式提供不同的头文件
5.支持编译依存性最小化的一般构想是:相依于声明式,不要相依于定义式,基于此构想的两个手段是handle class 和 interface class
6.程序库头文件应该以完全且仅有声明式的形式存在,这种做法不论是否设计template都适用。


条款32:Make sure public inheritance models “is-a”.确定你的public继承塑模出is-a关系

        请记住:

“public继承”意味is-a。适用于base classes身上的每一件事情一定适用于derived classes身上,因为每一个derived class对象也都是一个baseclass对象。


条款33:Avoid hiding inherited names.避免遮掩继承而来的名称

请记住:

1. derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。

        2. 为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions)。


条款34:Differentiate between inheritance of interface and inheritance of implementation.区分接口继承和实现继承

         1. 成员函数的接口总是会被继承。

2. 声明一个pure virtual函数的目的是为了让derived classes只继承函数接口。

3. 声明简朴(非纯)impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。

4. 声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性声明。


条款36:Never redefine an inherited non-virtual function.绝不重新定义继承而来的non-virtual函数

        

条款37:Never redefine a function’s inherited default parameter value.绝不重新定义继承而来的缺省参数值

        请记住:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。


条款38:Model “has-a” or “is-implemented-in-terms-of” through composition.通过复合塑模出has-a或“根据某物实现出”

        请记住:

1. 复合(composition)的意义和public继承完全不同。

        2. 在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。


条款39:Use private inheritance judiciously.明智而审慎地使用private继承

        请记住:

1. Private继承意味着is-implemented-in-term-of(根据某物实现出)。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数里,这么设计是合理的。

        2. 和复合(composition)不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。


条款40:Use multiple inheritances judiciously.明智而审慎地使用多重继承       

如果使用多得继承,会出现歧义,解决方法:采用virtual继承。

对于virtual base classes(亦相当于virtual继承)的忠告:

第一、非必要不使用virtual base。平常请使用non-virtual继承。

第二、如果你必须使用 virtual base class,尽可能避免在其中放置数据。这么一来你就不需要担心这些classes身上的初始化(和赋值)所带来的诡异事情了。

请记住:

1. 多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。

        2. virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。

        3. 多重继承的有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个实现的class”的两相组合。


原创粉丝点击