Effective C++

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/shhchen/article/details/88555393

闲话:C++程序员分为两类:读过Effective C++和没读过的。

 

条款1:视C++为语言联邦

过程、对象、泛型、元编程、函数式;

对内置类型而言,pass-by-value通常比pass-by-reference高效;

对于用户自定义的对象,由于构造和析构的存在,pass-by-reference-to-const往往更好;

对于小的用户定义类型,也不一定是pass-by-value好,因为考虑到以后的变化(版本更新);还有编译器不一定把内建类型和自定义的一视同仁,即使只包含一个double;

条款2:const、enum、inline替换#define

    #define MAX(a,b) func((a)>(b)? (a):(b)),如果是++a,则会累加两次;可以考虑内联函数解决;

 

 

条款3:尽量使用const

1.const修饰指针,const point / const data;

2.STL迭代器: const std::vector<int>::iterator iter;与 std::vector<int>::const_iterator iter;

3.函数返回常量时,可以用const修饰,保证安全;

4.const修饰成员函数,函数参数不可变;

5.const修饰成员函数的返回值;

 

条款4:对象使用前初始化

1.永远在使用对象前初始化,内置类型也必须手动初始化;

2.成员变量的初始化发生在进入构造函数本体前;

3.在初始化列表初始化所有成员变量,如果是const或reference,就必须初始化;

    初始化动作是发生在进入构造函数之前的。

Class A {     Public: A(int a) _a(a) {     //这里不是初始化 } Private: int _a; }

对象实例单一时,使用单例模式;

 

条款5:编译器自己会创建默认默认构造函数、copy构造函数、赋值运算符

但是对于内含reference成员或const的class,需要自定义copy assignment函数; 

 

条款6:可以明确告诉编译器不要生成默认的赋值构造函数

    方法:自己声明private的copy构造函数和copy assignment构造函数,这样能够避免别人调用时编译器自动生成;

或者继承uncopyable,这样子类就不能使用copy构造函数和copy assignment构造函数,优点是错误提前到编译期;(看书理解)

 

条款7:多态基类声明virtual析构函数

如果需要在运行期决定需要用哪个对象或者virtual函数,则需要virtual这个特性(即带多态性质的);

    防止内存泄漏,只释放了基类而没有释放继承对象的内存;

虚函数表、指针vptr;

每一个带有virtual函数的类class都有一个vptl;(注意:是类!非对象)

增加了对象体积;

继承STL容器时,尤其注意这些容器都是non-virtual的;

纯虚函数与抽象类;

 

条款8:别让异常逃离析构函数

捕捉异常;并调用一个函数处理该异常;

    Dbconn::~Dbconn(){Try...catch...}

 

条款9:不在构造和析构函数中调用virtual函数

    先构造基类时,执行的虚函数是基类的虚函数,而不是子类的虚函数

 

条款10:重载operator=返回一个*this

Obj A;

A = B = C;

 

条款11:在operator=中处理自我赋值(copy and swap)

Class A{ Private:     B* pb; } A& A::operator=(const A& A1) {     if(this == &A1) return *this;//去掉这一句会异常,因为自我赋值时pb指针为空       Delete pb;     Pb = new B(*A1.pb);     Return *this; }

copy and swap;//先成功造出来,再迁移(P56)

widget temp(rhs);

swap(temp);

 

条款12:复制对象时勿忘每一个成分***

复制每一个local成员变量;

    尤其是在子类的复制的时候,不要忘记父类;

    Copy构造函数的初始化列表加上:Father(son)  

    赋值函数加上:Father::operator=(son);

copy assignment不要调用copy构造函数,因为这相当于构造一个已经存在的对象;

 

资源管理

资源管理类很重要!经常用到智能指针

条款13:以对象管理资源

资源取得时机便是初始化时机;

  1. 获得资源后放入管理对象中(智能指针auto_ptr)
  2. 管理对象运用析构函数释放资源(离开区块后,智能指针调用析构函数)

智能指针特性:调用复制函数时,原来的智能指针指向空,这样保证了指针对对象唯一的拥有权

 

Share_ptr替代了auto_ptr,引入了计数机制,无人指向的时候销毁资源

 

条款14:资源管理类中小心copy行为

    例如:自定义一个锁类LOCK,如果lock对象被复制,则会发生两次析构,异常发生。

解决办法:禁止copy、引入share_ptr、深度拷贝、控制权转移;

 

条款15:资源管理类中提供对原始资源的访问

    例如:你引入了智能指针shared_ptr<myInvestment> pInv(CreatInvestment()),后面又需要处理myInvestment对象,存在一个函数func(myInvestment*),但是你不能够直接这样调用 func(pInv),因为func函数需要的是myInvestment*,而不是shared_ptr<myInvestment>,所以你需要进行类型转换。

func(pInv.get()),取得原始指针。

 

还有一个隐式转换

class Font{ public:     operator FontHandle ()const{return f;}//隐式转换 …… Private: FontHandle f; };   Font f1(getFont()); FontHandle f2=f1;//发生了隐式转换

 

条款16:成对使用new和delete时形式要相同

    new一个对象时,delete一个对象;new []数组时,delete[]数组;

 

条款17:以独立语句将new置入智能指针中

    就是先执行:std::tr1::shared_ptr<A> pw(new A);

    然后调用函数: processA(pw,priority());

    不要直接这样做:processA(new A,priority());因为不能隐式转换;

    还有另一种情况,资源泄漏,看书吧!

 

C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。

 

设计与声明

 条款18:让接口容易被正确使用,不易被误用

    日期接口对比:Date(int month,int day,int year);

                Date d(13,20,2018);//error

    用函数代替对象

    Date d(Month::Feb(),Day(20),Year(2018))

   

    不要过于依赖人力因素,很多时候通过代码设计避免问题

    例如:shared_ptr<myInvestment> CreatInvestment(),这个工厂函数强迫返回一个智能指针,防止了用户忘记使用智能指针,造成内存泄漏

 

    智能指针夹带删除器,结束时会自动调用删除器

 

条款19:设计class犹如设计type

    意思就是要谨慎设计,像当初语言内置类型设计一样

    注意:1、对象的创建和销毁

  1. 初始化和赋值的区别
  2. 对象被值传递
  3. 合法值,约束条件、异常
  4. 继承关系(virtual 析构)
  5. 转化行为
  6. 操作符行为
  7. 成员(private、public、protect)+

 

条款20:用引用传递替换值传递

    调用函数时,用引用传递替换值传递,明显减少构造和析构次数,提高程序效率;如果不希望传入对象被修改,加入const。

    还有对于有继承关系的类,如果值传递一个子类的对象,则会发生对象切割,子类的函数没有被调用,而是调用了父类的函数;改成引用传递就不会。

    对于内置类型、stl迭代器和函数对象,值传递好些。

 

条款21:必须返回对象时,别妄想返回reference

    有时候是不能返回ref的,因为可能存在资源提前释放或泄露问题

    W=x*y*z,*运算符,最好返回普通对象,否则资源提前释放

 

条款22:成员变量声明为private

    实现封装。

    Public变量变化,所有客户码都要变化;

    Protect变量变化,只有子类代码需要变化

 

条款23:以non-member、non-friend替换member函数

 

条款24:若所有参数需要类型转换,采用non-member函数

    Res= x*2; res=2*x;第二个执行失败,第一个成功,因为2是int类型,与x类型的*运算不相同;

    所以声明一个non-member函数,就能解决问题

 

条款25:写一个不抛异常的swap

    传统swap速度慢,中间有copy函数和=运算符

    总的来说就是:利用新类保证原来数据类的指针,新类提供member函数swap(b)用于指针交换;同时为了用户使用简易性,同一个命名空间加入swap(a,b)的non-member函数

详细看该链接:

http://blog.csdn.net/u011058765/article/details/46663827

  考虑写出一个不抛出异常的swap函数

本节讲解如何自定义一个高效的swap函数

对于std名空间中的swap缺省函数如下所示

namespace std{     template<typename T>     void swap(T& a, T& b)     {         T temp(a);         a=b;         b=temp;     } }

 

class WidgetImpl{ public:     …… private:     int a,b,c;              //数据很多,复制意味时间很长     std::vector<double> b;     …… };

·        我们可以看到,当交换对象的数据很大时,以上swap的执行效率会很慢,为了改变这种状况,作者提出以下方法;

重新定义一个类

class Widget{ public:     Widget(const Widget& rhs);     Widget& operator=(const Widget& rhs     {         ……          //复制Widget时,复制WidgetImpl对象                      *pImpl=*(ths.pImpl);         ……     }     …… private:     WidgetImpl* pImpl;//指针,含有Widget的数据 };

如果置换两个Widget对象值,只需要置换其pImpl指针,那么效率就会很高。 

那么怎样定义swap呢? 

如下:

calss Widget{ public:     ……     void swap(Widget& other)     {         using std::swap;//这个声明有必要         swap(pImpl, other.pImpl);     }     …… }; namespace std{     template<> //修订后的swap版本     void swap<Widget>(Widget& a, Widget& b)     {         a.swap(b);  //调用其成员函数     } }   Widget w1,w2;   swap(w1,w2);//调用名空间的全特化Widget版本的swap.

从上面的代码我们可以看出完成了我们的目的,实现两个WidgetImpl对象交换,只交换他们的指针。当然这里需要构造出一个交换类Widget,需要全特化Widget 类型的swap,需要在函数体内定义member函数swap。

那么,读者就迷茫了,绕了一大圈,怎么不直接调用成员函数swap,这是因为当你用swap(a,b)语句时,它会拥有自动合适匹配的功能。更适合客户使用,实现接口的一致性和简易性。

下面我们来介绍当两个类都是template,如何解决上面的问题? 

如下:

template<typename T> class WidgetImpl{……}; template<typename T> class Widget{……};

我们可以重新命名一个名空间如下:

namespace WidgetStuff{    ……//模板化的WidgetImpl等    template<typename T>//内含swap函数    class Widget{……};    ……    template<typename T>    void swap(Widget<T>& a,//non-member,不属于std命名空间              Widget<T>& b)    {        a.swap(b);    } }   Widget<int> w1,w2;   swap(w1,w2);//调用名空间WidgetStuff的Widget<int>版本的swap.  

 

5实现

 

条款26:尽可能延后变量定义式的出现时间

    1.延后直到需要使用该变量,最好能将它初始化。

2.对于循环的变量定义,

定义在外面:1次构造和1次析构、n次赋值

定义在里面:n次构造和n次析构

 

条款27:尽量少做转型动作

    C++新式转型

const_cast<T>(exp)

将对象常量性转型

dynamic_cast<T>(exp)

安全向下转型 继承体系,实现Base* to derived*

Reinterpret_cast_cast<T>(exp)

低级转型  ,int* to int

static_cast<T>(exp)

强迫隐式转换 例如:void* -》typed指针、基类指针-》子类指针

 

尽量避免转型,特别是dynamic_casts;如果转型是必要的,将其隐藏在函数背后;使用新式转型,不用旧式转型;

 

条款28: 避免返回handles指向对象内部的成分

handler(包括reference、)

带来危险,可能会修改到对象的private变量;

    有时候返回了引用,可以修改对象的内部数据

 

条款29:为异常安全而努力是值得的

    考虑出现异常的情况,因为异常一旦出现,你的资源可能泄露,数据丢失

    Copy and swap,先做副本,修改,成功后swap

 

条款30:透彻了解inline

    热衷使用inline造成程序体积过大,增加换页行为、缓存命中降低

    小型、被频繁调用的函数可以用inline

 

条款31:将文件间的编译依存关系降至最低

    使用类的前置声明

    实现类用指针指向

1.接口与实现分离 pimpl手法

    2.纯虚基类、接口类、抽象基类

 

6 继承和面向对象设计

条款32:确定你的public继承塑模出is-a关系

子类以public继承基类,告诉了编译器,每一个子类对象同时都是基类的对象。

Person和student,student is a person;

使用public继承时, base类中的每个方法都能在derived上使用;

矩形与正方形;

has-a、is-implemented-in-terms-of。

 

条款33:避免遮掩继承而来的名称

    实际运作:子类作用域套嵌在基类的作用域内

    子类的函数名称会遮盖基类的同名函数

    引入using base::func,仍然可以使用基类的函数

    或者直接base::func

 

条款34:区分接口继承和实现继承

    纯虚函数目的是被子类继承当函数接口,子类必须实现它;

    非纯虚函数可以被继承,缺省实现

    非虚函数:继承接口,强制实现

 

条款35:考虑virtual函数以外的其他选择

    NVI手法、template method设计模式

    用一个函数包装一个被子类继承的虚函数。

class GameCharacter{     public:         int healthValue() const         {             ……  //做事前工作             int retVal=doHealthValue();//真正做实际工作             ……  //做事后工作             return retVal;         }         ……     private:         virtual int doHealthValue() const //derived classes可以重新定义         {             ……         } };

 

    Strategy模式

    class GameCharacter;//forward declaration     int defaultHealthCalc(const GameCharacter& gc);//健康计算缺省算法     class GameChaaracter{     public:         typedef int(*HealthCalcFunc)(const GameCharacter&);         explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc)             :healthFunc(hcf)         {}         int healthValue()const         { return healthFunc(*this); }         ……     private:         HealthCalcFunc healthFunc;//函数指针     };

藉由tr1::function完成Strategy模式

策略模式:把算法用一个类包装起来

这样传入的就可以不一定是函数指针了,还可以是函数对象、成员函数等等(泛化的指针)

typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;

 

条款36:绝不重新定义继承而来的non-virtual函数

    non-virtual函数是静态绑定的,virtual才是动态绑定

 

条款37:绝不重新定义继承而来的缺省参数值

    缺省参数值是静态绑定的!子类继承父类的virtual函数,默认参数依然是父类的默认参数,而不是子类的默认参数。

    那如果子类的虚函数想用默认参数,那不就用不了?!

    解决办法:用NVI手法,用non-virtual函数包装virtual函数

 

条款38:通过复合塑模出has-a或“根据某物实现出”

  应用域,复合意味着has-a;

实现域,复合意味着is-implemented-in-terms-of;

 

条款39: 明智而审慎的使用private继承?

如果类之间的继承关系是private,编译器不会将一个继承类对象转换为一个基类对象,这意味着private继承并不是is-a的关系,意味着

是is-implemented-in-terms-of。

1.  class Person     2.  {     3.  protected:     4.      string name;     5.  };     6.       7.  class Student:private Person     8.  {     9.  private:     10.     string schoolNumber;     11. };     12.      13. void eat(const Person& p)     14. {     15.     cout<<"eat"<<endl;     16. }     17.      18. void study(const Student& s)     19. {     20.     cout<<"study"<<endl;     21. }     22.      23. int main()     24. {     25.     Person p1;     26.     eat(p1);     27.     Student s1;     28.     study(s1);     29. //  eat(s1);错误     30.     return 0;     31. }    

Private继承好处,即base class 大小为空时,调用private继承可以使派生类占据的空间大小最小化

 

条款40:明智审慎使用多重继承

    多重继承容易导致歧义;

    两重拷贝;

非必要不要使用virtual bases,virtual 继承造成class体积过大;

"public继承某个Interface class"和"private继承某个协助实现的class"的两点相组合

1.  class IPerson { // this class specifies the   2.  public: // interface to be implemented   3.      virtual ~IPerson();   4.      virtual std::string name() const = 0;   5.      virtual std::string birthDate() const = 0;   6.  };   7.  class DatabaseID { ... }; // used below; details are   8.  // unimportant   9.  class PersonInfo { // this class has functions   10. public: // useful in implementing   11.     explicit PersonInfo(DatabaseID pid); // the IPerson interface   12.     virtual ~PersonInfo();   13.     virtual const char * theName() const;   14.     virtual const char * theBirthDate() const;   15.     virtual const char * valueDelimOpen() const;   16.     virtual const char * valueDelimClose() const;   17.     ...   18. };   19. class CPerson: public IPerson, private PersonInfo { // note use of MI   20. public:   21.     explicit CPerson( DatabaseID pid): PersonInfo(pid) {}   22.     virtual std::string name() const // implementations   23.     { return PersonInfo::theName(); } // of the required   24.     // IPerson member   25.     virtual std::string birthDate() const // functions   26.     { return PersonInfo::theBirthDate(); }   27. private: // redefinitions of   28.     const char * valueDelimOpen() const { return ""; } // inherited virtual   29.     const char * valueDelimClose() const { return ""; } // delimiter   30. }; // functions  

 

7模板与泛型编程

条款41:隐式接口和编译期多态

    编译期多态和运行期多态

       平常的多态就是运行期多态;模板就是编译期多态,函数直到编译的时候才根据对象来确定

    显示接口和隐式接口

    void DoSometing(T& w) 

    if(w.size()>10&&w!=someNastWidge) 

    { 

        T temp(w); 

        temp.normalize(); 

        temp.swap(w); 

    } 

    显示接口(explicit interface): 源代码可以找到这个 interface(接口)(例如,Widget 的 .h 文件)以看清楚它是什么样子的,可以找到w对象的size()源代码实现,所以我们称其为一个 explicit interface(显式接口)——它在源代码中显式可见。

    隐式接口:用模板实现,找不到源代码实现,因为size()是编译时确定的。

template<typename T> void DoSometing(T& w) {  if(w.size()>10&&w!=someNastWidge)  {   T temp(w);   temp.normalize();   temp.swap(w);  }

 

条款42:了解typename的双重意义

    用模板时,class和typename基本没有不同

1.  template <typename C> void print2nd(const C& container)   2.  {   3.      if(container.size() >= 2)   4.      {   5.          C::const_iterator iter(container.begin());   6.          ++iter;   7.          std::cout<<*iter;   8.      }   9.  } 

以上代码似乎没问题,但最好在const_iterator前加上typename

Typename C::const_iterator iter(container.begin());  

告诉c++ const_iterator是个类型,不是变量;

Typename标识套嵌从属类型名称

 

条款43:学习处理模板化基类的名称

    基类模板可能被特化,成员可能发生改变,所以c++禁止子类模板直接寻找基类模板的成员。

解决办法:this指针、using 基类模板的成员、明确指出作用域(修饰)

 

条款44:将与参数无关的代码抽离templates

    使用template可能会导致代码膨胀

SquareMatrix<double, 5> sm1;  sm1.invert();  SquareMatrix<double, 10> sm2;  sm2.invert();

这里生成了两份代码

 

条款45:运用成员模板兼容所有类型

template<typename T>     class SmartPrt{     public:         explicit SmartPtr(T* realPtr);         ……     }; SmartPtr<Top> pt1=SmartPtr<Middle>(new Middle);   template<typaname T>     class SmartPtr{     public:         template<typename U>         SmartPrt(const SmartPrt<U>& other)         :heldPrt(other.get()){};         T* get() const{return heldPrt;}         ……     private:         T* heldPrt;     };   SmartPtr<Top> pt1=SmartPtr<Middle>(new Middle);

 

 

条款46:需要类型转化时请为模板定义非成员函数

    定义函数模板后,在使用该模板时,不要出现隐式转化的情况,因为此时函数模板还没有具现化出来;那怎么解决呢?我们可以把该函数模板声明为一个类的成员函数,这样存在隐式转化时,函数已经被具现化了。最好把函数的定义inline到类中。

 

8 定制new和delete

条款49:了解new-handler的行为

当new操作分配内存失败时,返回null指针,同时跑出异常,通过new-handler处理。

new_handler set_new_handler(new_handler p)

 

条款50:了解new-handler的行为

 

条款51:了解new-handler的行为

 

条款52:了解new-handler的行为

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

展开阅读全文

没有更多推荐了,返回首页