Effective c++ 读书笔记

  1. 不明确的行为,强制取地址或者值,(void*)value等。
  2. 注意隐式转换,表明构造,关键字explicit
  3. 命名习惯,类成员变量,指针同意 m_p 开头,常量同意 _ 开头
  4. RT1和Boost库

一:让自己习惯c++

  • 1.c++是一个语言联邦。过程,面向对象,函数式,泛型编程,元编程4种组成
  • 2.尽量以const,enum,inline代替#define。#define可能会被预编译器移走,在编译器开始的时候,#define并不具备封装性
  • 3.取enum或者#define的地址是不合法的
  • 4.不管任何时候,宏定义的参数(函数),都加上小括号。然而它还是有很多弊端,避免使用宏定义的一些函数,使用inline也不错
  • 5.尽量使用const,如果想要在const函数内更改成员变量数据,使用关键字mutable。
  • 注意:在操作符等运算中,如果有2份函数做同样的功能1是const,2是非const,。可以将2转换为1,会调用const1,在接触const限定符,节省重复代码,即让null-const调用const来生成孪生兄弟的做法,最好不要反着来
  • 6.使用对象或者变量之前(static),应该初始化
  • 7.extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。另外,extern也可用来进行链接指定。但是得确定当前使用的地方要比extern修饰的对象初始化要晚,不然灾难,一般会用static的指针或者引用

二:构造/析构/赋值运算
  • 1.明确拒绝编译器的一些行为:比如阻止拷贝等。声明但是不实现
 class A
{
 .....
privare:
A (const&);
A &operator=(const A&)
}
  • vritual的多态性,包括析构函数,但是函数体积会增大50%--100%,妄图继承一个不带vritual的父类,删除父类指针,并不会删除派生类,是因为没有让析构呈多态性,string,STL等都不带vritual析构,不要试图继承。。常用准则是,如果class带有任何的virtual,它都应该声明一个virtual析构

  • 不要让异常逃离析构,但是如果异常在析构函数崩溃,可能会引起内存释放等不明确行为的问题,1所以析构绝对不要吐出异常,如果可能有异常行为,应当捕捉,吞下异常或者结束程序。2将异常放在普通函数中处理

  • 不要在构造或者析构中调用virtual函数,在构造期间virtual函数不会呈现多态性为

  • 令赋值操作符返回一个指向*this。但是应当做一个复制前的检测
          wight &operator = (const wight & rhs)
         {
               if(this==&rhs)return *this;
               delete obj;
               .............
               ...........
         }
          当然也有使用copy and swap技术

  • 当在自写copy函数的时候,确保复制所有的本地变量;如果是派生类,则要确保调用base类copy函数的成员数据是否复制到。注意赋值操作符中调用copy函数是不合理的,这是试图构造一个已经存在的对象;如果在赋值操作符中调用copy函数,这是在操作一个还未构造出来的对象在赋值,error的。如果赋值操作和copy操作有相似的代码,可以新建个init,供他们共同调用,减少代码重复。

三:资源管理
  • 导读,内存是常见的资源之一,其他还有文件描述器,互斥锁,图形界面的字型和笔刷,数据连接,以及sockect
        
  • 以对象管理资源:可以使用RAII来管理对象,RAII常用有share_ptr和auto_ptr(c11中貌似改变了),前者通常是较佳的选择,后者,复制可能使被复制物指向null

  • 在资源管理类中小心copy行为:详见page69

  • 在资源管理类中提供原始的资源访问。一般访问经由显示或者隐式的转换。显示较为安全,隐式有漏洞但是较为方便

  • 成对new,delete,使用相同的形式

  • 以独立的语句将new对象放入智能指针中,涉及到赋值先后问题  page76

四;设计与声明
  • 让接口容易被正确使用。较佳的接口设计原则是先发制人,堵住歧义;
         让接口内置类型的行为兼容;
         阻止误用的办法包括新建类型、限制类型操作,束缚对象值,以及消除客户的资源管理责任;
         shared_ptr支持定性删除器,可被用来自动解除互斥锁等。

  • 设计class犹如设计type
          新type的对象应当如何被创建和销毁;
          对象的初始化和对象的赋值应当有什么差别;
          新type的对象被以值传递,意味着什么
          什么是新type的合法值
          新type需要配合某个继承体系吗?
          新type需要什么样的转换
          什么样的操作符和函数对新type而言是合理的
          什么样的标准函数应当驳回,声明private的必要
          谁改取用新type的成员,public,friend等嵌套合理性
          什么的新type的未声明的接口
          新type有多么一般化,定义一个type还是一个type家族,如果不是则定义class template
          真的需要一个新的type吗?

  • 宁以传递引用替换传递值的方式
          尽量以传递引用的方式替换值传递,可以防止切割的问题
          这个规则并不适合内置类型,以及STL的迭代和函数对象,对他们而言,传递值往往比较适当

  • 必须返回对象时,不要返回引用
          将变量声明为private
          不要在返回引用或者指针指向一个local对象,不要在不是单利中返回static,或者new(特别是在operator中)

  • 将成员变量声明为private,封装的重要性

  • 宁以非成员、友元函数替换成员函数,更强的封装性
          面对对象守则:数据是尽可能的被封装,越少人看到它,就有越大的弹性去改变它
          将所有的便利函数放在多个头文件但是隶属同一个命名空间,意味着客户端可以轻松的扩展这些函数
          
  • 所有的参数皆需类型转换,请采用非成员函数,比如无理数和int的乘积等等,隐式转换,有时候一个非成员函数更为快捷
          
  • 考虑写一个不抛异常的swap函数  page112.

五:实现
  • 太快的定义变量可能造成效率上的拖延;过度使用转型可能导致代码变慢有难维护;返回内部数据可能破坏封装;未考虑异常带来的冲击则可能导致资源泄漏和数据败坏;过度的inline使得代码膨胀;过度耦合可能导致让人不满意的冗长建制时间

  • 尽可能的延后第一变量定义式的出现时间,可以增加程序的清晰度并改善程序效率

  • 尽量少做转型动作  page119
          如果要使用也应当把他们隐藏在某个函数背后,让客户自由的调用
          就算使用转型也使用新式转型,更容易让人识别出来

  • 避免返回handles指向对象内部成分,可能悬空

  • 为异常安全而努力是值得的。异常安全2个准则:不泄露任何资源;不允许数据破坏
          如果系统内有一个函数不具备异常安全性,整个系统就不具备异常安全性
          异常安全函数3个基本保证:基本型,强烈型,不抛异常型
          强烈保证往往能够以copy-and-swap 来实现,但是强烈型并非对所有函数都实现或具备现实意义
          函数提供的异常安全往往等于各个函数异常安全的最弱者(木桶中的最短板)

  • 透彻了解inlining函数
          inlining函数不需蒙受函数调用所致的额外开销,但是过多的inlining函数会增加目标码的大小,对内存有限的机器会招来效率的损失
          inlining函数只是对编译器的一个申请,不是强制命令,可以隐晦的提出也可以强制提出
          inlining函数大多属于编译期的行为
          大多数inlining函数都是限制在小型、被频繁调动的函数身上

  • 将文件间的编译依存关系降至最低   page142

六:继承与面向对象设计
  • 确定你public继承塑模出is-a关系
          
  • 避免遮掩继承而来的名称
          查找变量或者函数作用域,就近查找,逐层上推
          派生类的内名称会遮挡基类的内的名称,可以使用using声明或者转交函数:
           class A
           {
               public:
                    virtual getStart()=0;
                    virtual getStart(int);
           };
           class B:public A
           {
             public:
                  vritual getStart(){...};
           }
         如果直接使用B b;  b.getStart(1);会出错。它已经被遮挡了
         可以使用using A::getStart;声明          或者 转交  virtual getStart(){A::getStart();}

  • 区分接口继承和实现继承
          声明纯虚函数的目的是为了让派生类只继承函数接口
          声明非纯虚函数是为了让派生类继承该函数的接口和缺省实现
          声明非虚函数是为了让派生类继承函数的接口以及一份强制性的实现

  • 考虑virtual函数以外的其他选择
          藉由非虚函数实现的手法实现模板(其实与模板没有关联)模式:令客户通过public的非虚函数间接调用private的虚函数=non-virtual interface(NVI)手法  page170
          藉由function pointers实现strategy(战略模式)  page172
          藉由tr1::function完成战略模式

  • 绝不重新定义继承而来的non-vitual函数

  • 绝不重新新定义继承而来的缺省参数值

  • 通过复合塑模出has-a或者某物实现出

  • private继承意味着根据某物实现出,但是尽可能的使用复合

  • 明智而审慎地使用private继承,一般是空的base class或者内涵typedef。enum、static变量等。private通常比复合级别低,但是当派生类要访问基类的保护成员或者重新定义而来的virtual函数时,可以使用

  • 明智而审慎地使用多继承
          如果要重新定义而来的vitual函数,可以将派生类的virtual置为私有

七:模板与泛型编程
  • 了解隐式接口和编译期多态 page202

  • 了解typename的双重意义
          任何时候当你想要在template中指涉从属类型名称,就必须在紧邻它的前一个位置放上关键字typename           page205
          但是typename不可以出现在base class list内的嵌套从属类型名称前,也不可以出现在成员初始列中作为base class的修饰符   page206

  • 学习处理模板化基类内的名称


你可能感兴趣的:(c++)