效率c++总结 参照2011版

Posted greenscarf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了效率c++总结 参照2011版相关的知识,希望对你有一定的参考价值。

关于c++基本

1、将c++视为语言联邦 它有4个层次:c、面向对象、泛型、stl库

2、对于单纯常量,最好用const对象或enums替换#define     enums为用户刻画了一组有范围的值

3、对于形似函数的宏,用inline代替#define

4、尽量、大胆地使用const,编译器强制实行bitwise constness(保证物理常量性),表现为在编译阶段检测有无非法的赋值语句,但编程应使用“逻辑常量性”  mutable 关键字可以解决部分问题

5、当const和non-const成员函数有实质等价的实现时,令non-const版本调用const版本来避免重复代码,这需要两次强转

6、上述条款涉及到 指针常量、常量指针、const iterator、const_iterator、返回常量的函数(避免a*b=c这样的代码)、const成员函数(不可修改this指针指向的内容)等知识

7、两个成员函数如果只是常量性不同,可以被重载:const char& operator[] (int size) const 与 char& operator[] (int size), 当对象为常量时  比如 const A a,将调用const版本的成员函数,尤其在void print(const A& a) 这类函数中体现。

8、为内置型对象手工初始化,c++不保证初始化它们,构造函数最好 使用成员初值列表(在构造函数中赋值可以达到同等效果,但不高效,因为调用了成员变量的赋值构造函数)次序最好与在类中的声明次序相同。对于const 成员变量和引用型成员变量由于不能赋值,只能在初值列表中初始化。

9、“跨编译单元值初始化次序问题”,用local static对象替换non-local static 对象。(单例模式的常见实现手法)

关于构造函数

10   编译器可能为你生成默认构造函数(没有自己声明任何构造函数时【包括带参、拷贝、赋值】)、拷贝构造、赋值构造、析构函数 这些都是public且inline的,如果bass类没有virtual析构函数,编译器生成的析构版本将是non-virtual的。

11、编译器会拒绝生成赋值构造函数,需要用户自己定义,这些情况包括:成员变量含有引用类型或const类型(因为这些变量不能被赋值,拷贝构造不会产生问题,因为它用初始化的方式为这些变量产生初值);bass类中=操作符声明为私有

12、编译器会拒绝生成拷贝构造函数,这些情况包括:bass类中拷贝构造函数为私有

13、若不想用编译器自动生成的函数,就明确拒绝:将拷贝、赋值构造函数声明为private且不实现,调用它们时将产生一个编译错误(无法访问)。但如果成员函数和友元函数调用它们,由于这些函数能访问到private,则会报链接错误(没有定义)。将链接错误移至编译期错误是可能的(这是好事):

  设计一个bass类,将拷贝、赋值构造函数声明为private,去继承它们,这样友元函数和成员函数就没有访问权了。

14、为多态基类声明virtual析构函数

15、关于多态,本条是自己的总结:基类为A,子类为B 。point A=B 时,将调用A的成员函数,如果A的成员函数是非虚的(虽然B的相同名字的成员函数是虚的),就不显现多态性,当A的成员函数是虚的,则去虚函数表去找函数入口,但是该对象的vpt依然指向B的虚函数表,故呈现多态性。

16、析构函数不应该吐出异常,这将导致程序意外终止或不明确的行为。如果析构函数可能吐出异常,应在析构函数中捕捉并吞下它们,但这样做没有提供客户处理异常的机会,解决方案是,提供一个普通函数,将可能产生异常的代码放入其中,由客户手动调用,这样就将处理异常的任务转给了客户。

17、在构造和析构期间,不应直接和间接调用任何虚函数,多态效果并不呈现:原因是构造完成前,会认为该类是base类(子类的成员变量并未被初始化),将调用base类的成员函数,一个代替方案是:在构造子类时传递必要信息给基类(在基类构造函数传参),使基类构造和子类构造有不同的结果     (比如打印信息不同)

18、令 operator= 返回 reference to *this

19、在 operator= 中处理“自我赋值”问题,这里引起两个安全问题:自我赋值安全性,异常安全性。加入“证同测试” 使其 自我赋值安全。必要的解释如下:

  A& A:operator=(const A& rhs) {

    if(this==&ths) return *this; //  证同测试

    delete pb;

    pb=new B(*ths.pb);

    return *this;

  }

  如果没有证同测试且ths==this,将导致 *this.pb 指向被删除内存。(自我赋值不安全)

  但是如果 pb=new B(*ths.pb) 引起异常,最终导致 pb指向一块被删除的内存。(异常不安全)

  解决异常不安全通常也能解决自我赋值不安全:

  A& A:operator=(const A& rhs) {

    B * pOrig=pb;

    pb=new B(*ths.pb);

    delete pOrig;

    return *this;

  }

  或者采用 copy and swap 技术。

20、复制对象时勿忘其每个成分,尤其容易忽略的地方是,子类忘记调用基类的拷贝构造和赋值构造。

关于资源管理

21、以对象管理资源 (RAII)例子是 智能指针,给要管理的对象比如A,穿上马甲 class A_Manager{private:A* m_a;}  在构造函数传入实例 ,在析构函数中释放。

22、小心在资源管理类中的copying行为:抑制copying、引用计数法

23、提供对原始底层的资源 访问接口。可以显式的写成get成员函数,也可以隐式的重载-> 和* , 提供隐式转换函数 operater() ;

24、成对使用new 和 delete 时 ,保证使用相同的形式

25、以独立语句将newed对象植入智能指针,考虑如下代码:

   A(shared_ptr<B>(new B), fun());

   编译器需要做三件事:执行new B,调用 智能指针构造函数 ,调用fun()。 但是编译完成这三句的次序不定(可以肯定 2在1之后,但是3 可以在任意位置,这取决于编译器) ,当次序为 1 3 2 时且 3 发送异常,导致 2并没有置入智能指针内,将发送内存泄漏。 所以最后分开写为:

   shared_ptr<B> pw(new B);

  A(pw,fun());

  这样编译器没有调整执行次序的自由。

设计与声明

26、让接口容易被正确使用,不易误用:

27、设计 class犹如设计type

  新type对象该如何创建和销毁:关系到operator new ,operator new[],operator delete ,operator delete [] 的重载

  对象的初始化和赋值的差别:决定了构造函数和赋值操作符的行为

  新type对象如果按值传递:决定了拷贝构造函数的实现

  新type的合法值:要求成员函数做出错误性检查

  新type需要配合某个继承图系:考虑析构函数的虚拟性

  什么样的操作符对新type是合理的:重载哪些操作符

  哪些标准函数应该驳回:用private 去屏蔽某些 函数

  谁该取用新type的成员变量:决定哪些成员是public、private、protect,friend

  什么是新type的未声明接口

  新type有多么一般化:决定是否模板化,泛型化

  真的需要新type吗:有没有其他能完成的方案,比如多个普通函数或模板函数

28、用传常引用方式(使用const的原因是避免实参改动)代替传值规避切割问题(传值通常调用了拷贝和赋值函数,导致新的对象产生,从而新对象的ptr指向基类虚函数表丢失多态性),但是对于内置类型和stl使用传值

29、不要返回指针或引用指向 局部栈上的变量,或者堆上分配的变量,返回*this是可以的。

30、将成员变量设为priavte,主要原因是封装,屏蔽用户对内部的可见性。

31、name space 是可以跨越多个cpp的,而class 不可以

32、尽量延后变量的定义式,尽量初始化,因为通过构造函数传参比 先用无参构造函数再赋值的效率高

33、const_cast 解除常量性,dynamic_cast 安全向下转型,成本很高,static_cast 强制隐式转型 ,reinterpret_cast 执行低级转型,例如将int转为int*

  尽量少做转型动作,特别是成本很高的安全向下转型。一个有趣的事实:

  当base*指向derived对象时,base*的值与原本的derived*的值可能不一样,它们存在一个偏移量,这个偏移量由平台决定。另一个问题是,调用base的虚函数,多态性将起作用,即便用了强制的隐式转换。

34、避免返回handles(包括:指针、引用等)指向对象内部成分,因为可能会影响封装性,即:这样做使客户可访问内部私有数据,返回的handles也可能发生虚吊(或称为 悬空)

35、关于异常安全性:不泄露任何资源(比如某代码抛出异常,导致后面的互斥锁无法释放,或者后面的释放堆空间代码无法执行),不破坏任何数据(某代码异常,导致前面的释放堆空间代码执行后,破坏了原数据,但后面的重新分配代码无法执行) 参看第19条

36、异常安全的代码必须提供三种保证之一,按保证强到弱分为:不抛出异常,强烈保证(如果异常抛出,程序状态并不改变,保证函数要么成功要么回滚),基本承诺(抛出异常时,保证)

37、copy and swap 技术(先对原对象copy,修改copy的副本,待完全成功后,用不抛异常的swap函数交换二者)可以达到强烈保证效果,但是不是所有的函数都可以实现

38、考虑写一个高效的不抛异常的swap方法,他是第37条的基础:对类提供一个成员函数swap,以高效的方式实现它(比如 只是对 指针内容的交换),提供一个非成员函数swap(特有模板化)去调用上述成员函数的版本。

39、将大多数inline函数现在在小型的频繁调用的函数身上。inline可以隐式指出和显式指出。

40、将文件间的编译依存关系降至最低的一般构想是相依与声明式,不要相依与定义式,两个做法是handle class 和 interface class,这解释了qt库的用法。考察下列代码:

  #include<A.h>

  #include<B.h>

  class C{

    A a;

    B b;

    C( A ,B);

  }

  上述代码中,若A的定义改变了,或A.h中有任何变化,将会导致本头文件和包括本头文件的其他文件重新编译。

  解决方案是:使用引用指针(handle) 代替对象,以class声明代替class定义,上述代码改变为:

  class A;

  class B; //前置声明

  class C{

    A* a;  //handle 类型

    B* b;

    C( A ,B);

  }

  这样,class C 的实现cpp将包含 A.h B.h 。不会导致大规模的重新编译

面向对象

41、public 的继承关系将会以 is a 塑模

42、避免遮掩继承而来的名称,本条款与15条不要混淆: 分析的时候先利用15条,再利用本条

  class A{
   public:
    virtual void x(){ cout<<"A::x"<<endl;}
    virtual void x(int i){cout<<i<<endl;}
    void y(){ x();cout<<"A::y"<<endl;}
    ~A(){
      cout<<"d A"<<endl;
    }
  };

  class B:public A{
    public:
      virtual void x(){ cout<<"B::x"<<endl;}
      virtual void y(){cout<<"B::y"<<endl;}
      ~B(){
        cout<<"d B"<<endl;
      }
  };

  B bbb;
  A*aaa=&bbb;

  aaa->y();    //由15条:y()为非虚函数,不应呈现多态性,调用A::y(),A::y()中调用x(),有x()是虚函数,呈现多态性,调用B::x()

  aaa->x(1);  //由15条:本该呈现多态性,但B并没有重写x(int),虚函数表中依然填入A::x(int)的地址,调用A::x(int)
  bbb.x(1);   
//由于名称覆盖,B中并没有x(int)  编译不通过

43、声明纯虚函数的目的是为了只继承接口,非纯虚的函数是为了继承接口和默认的实现(非纯虚函数提供了一个实现,但你可以用自己的实现),实函数的目的是为了继承接口和一份强制的实现(如果自己再实现一份,便会出现遮掩问题),私有的虚函数是为了仅仅继承一份实现而不继承接口。

44、绝不重新定义继承而来的实函数(non-virtual),这将发生遮掩(不会造成编译上的错误,但这表明基类的实函数可能是不必要的,或者应该是虚拟的):所以,多态性的基类析构函数如果没有写成虚函数,将违背此条款。

45、遮掩问题不仅是44条中描述的那样,另一个发生遮掩的情况如42条阐述的那样

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

  class A{

  virtual void x(int a=1) const=0;

  }

  class B:public A{

  virtual void x(int a=2){};

  }

  当基类指针A指向B的对象时,调用x() 实际将调用x(int a=1),及默认的缺省参数没有改变,原因是缺省参数是在编译期就决定好了的,无法支持运行时的动态行为,解决方案是 在x()外面套一个函数外衣,该函数是非虚的且有缺省参数。

47、多重继承比单一继承复杂,导致了新的 歧义性 问题,以及对虚基类的需要,虚基类继承会增加大小、速度、初始化、、赋值等成本,如果虚基类不带任何数据,是最好的做法,多重继承有正确用途,更多请查阅相关资料

定制new和delete

48、set_new_handler 允许客户指定一个函数,在内存分配无法获得满足时被调用

49、利用定制的new和delete(即重载new operator) 可用于:

  检测运用错误

  收集统计信息

  增加分配与归还速度

  降低默认内存分配器带来的额外空间开销

  弥补默认分配器的非最近对齐

  将相关对象 集簇

  获得非传统行为

50、定制new的规则:operator new 应该包含一个无穷循环,不停地尝试分配内存,如果无法满足将调用new-handler。

51、定制delete的规则:要能够处理null指针 

52、写placement operator new(含有额外的参数) 时 勿忘给出对应的delete 版本,在类中写placement版本时 不要覆盖标准的new(同时给出来即可),placement delete版本只会在伴随placement new调用成功,但随后的构造函数抛出异常时调用。

53、标准的operator new 包含:

  void * operator new(std::size_t) throw(std::bad_alloc);

  void * operator new(std::size_t,void*) throw();

  void * operator new(std::size_t,std::notrow_t&) throw();

 

模板与泛型

54、对类而言,接口是显示的,多态性通过虚函数技术发生于运行期,对模板而言,接口是隐式的,奠基于有效表达式,多态是通过目标具现化和函数重载,发生于编译期。

 

  

 

 

 

 

 

 

 

以上是关于效率c++总结 参照2011版的主要内容,如果未能解决你的问题,请参考以下文章

《More Effective C++》总结笔记

C++基础入门知识整理与总结

C++基础入门知识整理与总结

C++基础入门知识整理与总结

现代c++中实现精确延时方法总结

C++中 priority_queue 的用法总结