再看 Effective C++ 读书笔记

1、视C++为一个语言联邦
    C。区块、语句、预处理、内置类型转换、数组、指针等。相对于C++没有模板、异常理里、重载等。
    面向对象。类、封装、继承、多态、虚函数等。
    模板:泛型编程。
    STL。

2、尽量以const、enum、inline替换#define
    define有副作用,而且没有作用域限制,不能成为类的私有成员或函数的内部成员。
    define定义的宏名可能不会进入符号表,所以编译后无法查找该宏名,宏只是做了替换。如: #define  PI 3.14  解决之道是用常量替换:const double PI = 3.14;

    define不能创建一个类的专属常量,因为没有作用域限制,替代方法是:static const int Num = 5;在类的内部直接声明。

    针对宏定义的类似函数的宏而引发的副作用,可以用template inline 替换。

3、尽可能使用const 
    const可以定义常量,可以修饰函数的形参和返回值。实参是const类型时,形参必须是const类型。如果明确知道不需要改变形参的值,最好都加上const修饰。

    修饰类的成员函数,该成员函数不能修改类的数据成员。除此之外,const还可以用于函数重载。在于类对象,如果是非const修饰的,则调用非const函数,如果是const修饰的类对象,只能调用const成员函数。
    const 成员函数调用非const成员函数是错误行为,因为有可能改变对象。

4、确定对象被使用前已先被初始化
    使用内置类型要手动初始化,而对于其他对象,则要使用构造函数初始化。而初始化类表才能进行初始化,在构造函数体内进行的是赋值操作。如果成员变量是const或引用类型,也必须通过初始化列表进行初始化。

5、了解C++默认编写并调用哪些函数
    默认构造函数、拷贝构造函数、赋值操作符重载函数、析构函数。
    拷贝构造函数默认的是位拷贝,当类对象包含其他资源时,不能使用默认的拷贝构造函数,要自己编写构造函数,为对象创建属于自己的资源,而不是共享一个资源。赋值操作符重载函数也是一样的。
    如果类的成员变量有const类型或引用类型,则默认拷贝赋值操作符重载函数会出错。必须自己定义这个函数函数。

6、若不想使用编译器自动生成的函数,就该明确拒绝。
    将这些函数声明为private并不实现他们。

7、为多态基类声明virtual析构函数
    由于面向对象的特性,如果基类的析构函数没有设成虚函数,则在指向子类对象时,不会调用子类对象的析构函数释放对象。
    基类的析构函数定义为虚函数,则所有的派生类的析构函数均为虚函数。

8、别让异常逃离析构函数
    在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。应在析构函数里将异常处理掉:终止程序运行或者解决异常。

9、绝不在构造和析构过程中调用virtual函数
    基类的构造函数在构造期间virtual函数绝不会下降到派生类阶层。即构造函数执行的虚函数是基类的,而不是派生类的虚函数。同样也适用于析构函数。

10、令operator= 返回一个 reference to *this
    为了操作符= 的级联使用。a=b=c  因为引用可以作为左值使用。

11、在operator= 中处理“自我赋值”
    可以加上一句判断语句:if(this == &otherobj) return *this;  但是也要考虑new操作符抛出异常时的情况:应该先用一个临时指针变量保存当前指针值,直到new成功后才释放该指针指向的资源。

12、复制对象时勿忘其每一个部分。
    类的默认拷贝构造函数和赋值操作符重载函数是位拷贝的,如果类中含有其他资源,则必须把其他资源也要拷贝一份,而不能共享这份资源。
    派生类的拷贝构造函数和赋值操作符重载函数要主动调用基类的相应的函数,否则不会默认调用的。

13、以对象管理资源 : RALL资源获取时机就是初始化时机
    在函数体内,提前的return语句会跳过资源的释放语句,goto也一样,或者抛出的异常改变了控制流,而不再执行资源释放语句。这都导致了内存泄露。
    将资源放进对象内,当控制流离开时,对象的析构函数会自动释放那些资源。智能指针就是很好的资源管理方式。

14、在资源管理类中小心copying行为
    深拷贝和浅拷贝的区别

15、在资源管理类中提供对原始资源的访问
    有时候需要取得资源管理类对象中原始的指针,可以用get函数,或重载的-> 和 * 操作符函数。

16、成对使用new和delete时要采取相同形式
    new对应delete; new T[] 对应 delete[] ;
    对象数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。

17、以独立语句将newed对象置入智能指针。
    对于这种情况:
    int priority();
    void processWidget(std::shared_ptr<Widget> pw, int priority);
    调用方式:processWidget(new Widget, priority());是错误的,因为shared_ptr类的构造函数是explicit显示调用的,不能隐式转换。
    调用方式:processWidget(std::shared_ptr<Widget>(new Widget), priority());可以编译通过,但有可能导致内存泄露。
    如果执行方式是:先执行new Widget,再执行priority函数,在调用shared_ptr构造函数。当priority函数抛出异常时,new 产生的资源将会遗失,没有进入智能指针,导致内存泄露。
    正确的方式是先创建智能指针,再传递给函数调用:
    std::shared_ptr<Widget> pw(new Widget);
    processWidget(pw, priority());

18、让接口容易被正确使用,不易被误用
    促进正确使用的办法包括接口的一致性,以及与内置类型的行为兼容
    阻止误用的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
    shared_ptr支持定制型删除器。

19、设计class犹如设计type
    1.新type的对象应该如何被创建和销毁。 构造、析构、内存分配和释放函数
    2.对象的初始化和对象的赋值该用什么样的差别
    3.新type的对象如果被passed by value,意味着什么?拷贝构造函数用来定义一个type的passed by value该如何实现。
    4.什么是新type的合法值。进行错误检查
    5.你的新type需要配合某个继承图系吗?
    6.你的新type需要什么样的转换?
    7.什么样的操作符和函数对此新type而言是合理的。
    8.什么样的标准函数应该驳回。 那些正是必须声明为private者。
    9.谁该取用新type的成员。public、protected、private、友元、
    10.什么是新type的未声明接口。
    11.你的新type有多一般化。 是否使用模板

20、宁以passed-by-reference-to-const替换passed-by-value
    passed-by-value需要调用拷贝构造函数,当类比较复杂时,会影响效率。
    passed-by-reference-to-const使得不再需要调用拷贝构造函数,const保证了不会修改传递的对象,而且使用reference可以用来实现多态。
    对于内置类型,以及STL的迭代器和函数对象,使用 passed-by-value比较适当。

21、必须返回对象时,别妄想返回其reference
    当reference指向的是局部对象时,当局部对象销毁时,该reference会产生错误。
    绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap对象,或返回pointer或reference指向一个local static 对象而有可能同时需要多个这样的对象。

22、将成员变量声明为private
    为了更好的封装

23、宁以non-member、non-friend替换member函数
    增加封装性和包裹弹性和机能扩充性。

24、若所有参数皆需类型转换,请为此采用non-member函数
    为了满足只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。比如一个类重载了*操作符,当这样使用:obj*2时可以通过编译,它转化成obj.operator*(2),将2作为参数隐式转化成obj对象;而如果这样使用:2*obj则不能转化成2.operator*(boj)。
    而如果定义一个non-member的重载*函数 const obj operator*(const obj& lhs, const obj& rhs);则可以两者都编译通过。

25、考虑写出一个不抛弃异常的swap函数
    缺省的实现版本:
    namespace std{
         template<typename T>
         void swap(T &a, T &b)
         {
            T temp(a);
            a = b;
            b = temp;
         }    
   }
    这个缺省版本使用了一次拷贝构造函数和两次赋值操作符重载函数,当T类型内包含大量的资源复制时,效率较为低下。
    解决方法:
    1.提供一个public swap成员函数,让它高效地置换你的类型的两个对象值。这个函数决不能抛出异常
    2.在你的class或template所在的命名空间内提供一个non-member swap,并令它调用上述的swap成员函数。
    3.如果你正在编写一个class(而非class template),为你的class特化std::swap。并令它调用你的swap成员函数。

26、尽可能延后变量定义式的出现时间
    在真正使用的时候再定义变量

27、尽量少做转型动作
    c++提供的四种类型转换操作符:const_cast<T>  dynamic_cast<T>  reinterpret_cast<T>  static_cast<T> 
    dynamic_cast通常是因为想在一个认定为derived class对象身上执行derived class 操作函数,但手上却只有一个指向base 的pointer或reference,这时只能用它们来处理对象。

28、避免返回handles指向对象内部成分
    reference、指针和迭代器都是所谓的handles,而返回一个代表对象内部数据的handle,随之而来的是降低对象封装性的风险。同时,也可能导致“虽然调用const成员函数却造成对象状态更改”。还有就是对象销毁后,这些handle就没有了意义。

29、为异常安全而努力是指得的
    当异常被抛出时,带有异常安全性的函数会:不泄露任何资源(可以以对象管理资源解决); 不允许原数据败坏。
    异常安全函数提供的三个保证之一:
    基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。当异常抛出时,数据仍然要保持之前的状态
    强烈保证:如果异常被抛出,程序状态不改变。如果函数成功,就是完全成功,如果函数失败,程序会恢复到调用函数之前的状态。
    不抛掷保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能
    
    强烈保证的策略:copy and swap :为打算修改的对象作出一份副本,然后在那副本上做一切必要修改。若有任何修改动作抛出异常,原对象仍然保持为改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换swap。

30、透彻了解inlining的里里外外
    inline函数最大的好处就是免除了函数调用的成本,但是和宏一样,使得程序代码增大。
    inline函数只是对编译器的一个申请,不是强制命令。在类中定义的成员函数是inline的。
    明确声明inline函数的做法是在其定义式前加上一个关键字inline,并且inline函数的定义要放在头文件中,调用该函数的程序要包含该头文件。
    inline在程序执行前,先将调用动作替换为调用函数本体,而虚函数则在运行期才确定调用哪个函数,因此虚函数不能实现为inline

31、将文件间的编译依存关系降至最低
    如果使用object reference 或 object pointers 可以完成任务,就不要使用objects。
    如果能够,尽量以class声明式替换class定义式。
    为声明式和定义式提供不同的头文件。
 
32、确定你的public继承塑模出is-a关系
    C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味is-a的关系。
    适用于base class身上的每一件事情一定也适用于derived class 身上。
33、避免遮掩继承而来的名称
    derived class内的名称会遮掩base class内的相同的名称。为了使用base class同名的成员,可以使用using声明式或转交函数(使用::操作符)。
34、区分接口继承和实现继承
    声明一个pure virtual函数的目的是为了让derived class只继承函数接口。
    声明non-pure virtual函数的目的是让derived class继承该函数的接口和缺省实现。
    声明non-virtual函数的目的是为了让derived class继承函数的接口以及一份强制性实现。
    基类中的纯虚函数即便是有实现,基类还是抽象类,派生类如果没有实现该纯虚函数,依然是抽象类。
35、考虑virtual函数以外的其他选择
    令客户通过public non-virtual 成员函数间接调用private virtual函数
36、绝不重新定义继承而来的non-virtual函数
    non-virtual函数是静态绑定的,如果指向的对象的指针是base class类型,则调用的是Basel class的non-virtual函数;如果指向对象的指针是derived class类型,则调用的是derived class的non-virtual函数。non-virtual函数的调用和指针指向的对象无关,只和指针的类型有关,因此不能体现出多态的特性。
    如果derived class 的同名成员函数确实和base class的成员函数不同,则最好是将base class的相应的成员函数设置为virtual类型的,这样函数实现的是动态绑定,在调用该函数时,只和指针所指的对象类型有关,和指针的类型无关。
37、绝不重新定义继承而来的缺省参数值
    缺省参数值都是静态绑定,而virtual函数时动态绑定。当derived class 重写virtual函数时,即便是修改了base class中virtual函数的缺省参数值,在真正调用该函数的时候,仍然认为缺省参数值是base class提供的值。
38、通过复合塑模出has-a或“根据某物实现出
    当复合发生在应用域内的对象之间,表现出has-a关系;当发生于实现域内则是表现出“根据某物实现出”关系。
 
 
39、明智而谨慎的使用private继承
    如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。第二条规则是,由private base class 继承而来的所有成员,在derived class 中都会变成private属性,纵使它们在base class 中是public 或 protected属性。
    private继承意味只有实现部分被继承,接口部分应略去。   
    应该尽可能使用复合,必要时才使用private继承。必要时是指当protected成员或virtual函数牵扯进来的时候。
 
    对于一个空类:class A { };它的大小一般为1,原因是因为编译器要求每个独立对象必须有不同的地址,因此经常默认安插一个char到空对象内。如果一个类内部有一个该空类类型的成员变量,则该成员变量至少占用1个字节,有时为了字节对齐可能占用更多的字节;但是如果一个类继承了该空类,则该空类不占用任何字节,即空白基类最优化。
 
 
40、明智而谨慎地使用多重继承
    菱形继承有诸多的问题,导致成员变量重复。 可以用virtual继承来避免重复,但是效率上会下降,以及增加初始化的复杂度。
 
 
41、了解隐式接口和编译期多态
    class和template都支持接口和多态。
    对class而言接口是显示的,以函数签名为中心。多态则是virtual函数发生于运行时期。
    对template参数而言,接口是隐式的,基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译时期。
 
 
42、了解typename的双重意义
    声明template参数时,前缀关键字class和typename可互换
    使用关键字typename标识嵌套从属类型名称;但是不得在base class lists或 成员初值列表内以它作为base class 修饰符。
 
 
43、学习处理模板化基类内的名称
    C++往往拒绝在模板化基类内寻找继承而来的名称。
    解决办法:
    1、在base class函数调用动作之前加上this->,  this->fun();
    2、使用using声明式  using Base<T>::fun();
    3.用::操作符明确告诉编译器调用base class的函数, Base<T>::fun();
 
44、将与参数无关的代码抽离templates
 
 
45、运用成员函数模板接受所有兼容类型
 
 
46、需要类型转换时请为模板定义非成员函数
 
 
47、请使用traits classes表现类型信息
 
 
48、认识template元编程。
 
 
49、了解new-handler行为
    当operator new无法满足某一内存分配需求时,会抛出异常。 bad-alloc
    可以在new的后面加上std::nothrow来使得在new分配内存失败的时候不抛出异常,而是返回NULL
    set_new_handle函数允许客户指定一个函数,在内存分配无法获得满足时候被调用。
50、了解new和delete的合理替换时机

 
51、编写new 和 delete 时需固守常规
52、写了placement new 也要写 placement delete
53、不要忽略编译器的警告
    成员函数加上 const 和不加 const  是完全两个不同的类型。
54、让自己熟悉包括TR1在内的标准程序库
    STL、  Iostreams、  国际化支持、  数值处理、  异常阶层体系、 
55、让自己熟悉Boost
 
 
继续补充。。。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值