zoukankan      html  css  js  c++  java
  • 效率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 对象。(单例模式的常见实现手法),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指向一块被删除的内存。(异常不安全)  

      解决异常不安全通常也能解决自我赋值不安全:  将delete置于分配新空间之后

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

     

      

     

  • 相关阅读:
    Kinect 开发 —— 硬件设备解剖
    Kinect 开发 —— 引言
    (转)OpenCV 基本知识框架
    OpenCV —— 摄像机模型与标定
    OpenCV —— 跟踪与运动
    OpenCV —— 图像局部与分割(二)
    OpenCV —— 图像局部与部分分割(一)
    OpenCV —— 轮廓
    OpenCV —— 直方图与匹配
    OpenCV —— 图像变换
  • 原文地址:https://www.cnblogs.com/GreenScarf/p/10830432.html
Copyright © 2011-2022 走看看