zoukankan      html  css  js  c++  java
  • c++ effective总结(一)

    条款一:视c++为一个语言联邦

      c++可以认为是由C,Object-Oriented C++(面向对象),Template C++(模板),STL(c++标准模板库)四种次语言组成的。

    条款二:尽量以const,enum,inline替换#define

      c++中推荐使用其他的方法替换一些宏定义操作,如常量定义,推荐使用const int MAX = 10 替换#define MAX 10。而对于#define定义的函数,也推荐使用inline(内联函数)替换。另外如在类中要声明成员属性的值。必须声明成静态常量,或者使用enum。如

    class Stack{
    public:
        ...
    private:
        static const int size = 10; // 或者 enum { size = 10 };
        int members[size];
    }

    条款三:尽可能使用const

      const在c++里面可以说是用的非常多的一个修饰词,const可以修饰值,指针,函数,函数参数,函数返回值等。

      如const char* p = "hello"; 指针指向的值为常量。char* const p = "hello";指向值的指针为常量。可以记为const在*左边值为常量,const在*右边指针为常量。如果*两边都有const,则值和指针都为常量。

      当你在使用stl容器时,使用迭代器访问容器中的元素,而又不希望通过迭代器修改容器中的值,可以使用const_iterator,或者是遍历常量容器对象,使用const_iterator。

      const修饰成员函数,该成员函数不可以修改成员属性值,另外const常用来传递的引用参数,当你不希望修改引用参数中的内容时,可以用const修饰。

    条款四:确定对象被使用前已先被初始化

      为内置对象进行手工初始化,因为c++不保证初始化它们,即你想初始化一个int对象,最好将int x;改写成int x = 0;当然大部分情况下c++会为int x;分配 x = 0。

      构造函数初始化成员变量时,最好使用成员初值列,这样效率比赋值要高一些,尤其是对非内置对象,内置对象两者没影响。如

    //推荐使用
    class Student{
    public:
        Student(string& name, int age):name_(name),age_(age) {};
    private:
        string name_;
        int age_;
    }
    
    //不推荐
    class Student{
    public:
        Student(string& name, int age) {
            name_ = name;
            age_ = age;
        };
    private:
        string name_;
        int age_;
    }

      上面第一种只有对于string对象只有copy操作,而第二种既有copy操作,还有赋值操作。

    条款五:了解c++默默编写并调用哪些函数

      你定义一个空类,编译器会为它分配default构造函数,copy构造函数,copy assignment操作符合一个析构函数。如

    class Empty {};

      实际上上面的类等价于

    class Empty {
    public:
        Empty() {...};
        Empty (const Empty& e) {...};
        ~Empty() {...};
        Empty& operator=(const Empty& e) {...};  
    }

       如果你自己定义了构造函数,copy构造函数,copy assignment,析构函数,则编译器不会再分配这些函数。

       编译器分配的copy assignment有时候存在问题,如string& name这样的成员属性,编译器分配的copy assignment是不可以赋值的,你需要自己重新定义copy assignment。

    条款六:若不想使用编译器自动生成的函数,就该明确拒绝

      如条款五所说编译器会自动分配如copy构造函数,copy assignment这类的函数,如果你不想要这些函数,可以在private下声明这些函数就行了。如

    class Student{
    private:
         Student (const Student&);
         Student& operator=(const Student&);
    }

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

      带有多态性质的基类,或者类中有带vitural的成员函数,则其析构函数都应该定义为virtual析构函数。但如果该类不是设计为基类,则不要声明virtual析构函数。如果基类中的析构函数没有定义为vitural,则会出现一些问题,如在工厂模式中。有以下代码

    class Person{
    public:
        Person();
        ~Person();
    }
    
    class Student : public Person {...};
    
    Person* getPerson();
    
    Person* student = getPerson();
    delete student;

      假设当前的getPerson()获取一个派生类Student对象,当使用完之后delete student时因为指针是基类指针,所以会销毁基类中的成员,但是不会销毁派生类中的成员,所以会存在”部分销毁“的现象,但是若基类中的析构函数是vitural,则会删除派生类中的成员。

      另外如果一个类不是基类,不要声明vitural函数,因为vitural会引入虚表指针和虚表,会占用一部分内存。

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

      这种通常指在析构函数中调用了一些函数,而这些函数可能引入异常,此时需要做一些处理,尽量保证不要让析构函数的异常传递出来,或者说尽量确保析构函数中不要发生异常。

    条款九:绝对不要在构造函数和析构函数中调用virtual函数

      因为上述这类调用绝对不会下降至下一层,即派生层,这种情况避免就好了。一般也不会这样去调用

    条款十:令operator= 返回一个reference to *this

      这是一种固定的assignment 操作符写法,如

    class Student{
    public:
        Student& operator=(const Student& s){
            name_ = s.name_;
            age_ = s.age_;
            return *this;
        }
    private:
        string name_;
        int age_;
    }

      除了operator= 如operator+=等都可以写成这样。

    条款十一:在operator= 中处理”自我赋值“

      自我赋值,即a = a;当然一般这种情况不会发生,但是这种*a = *b,而指针a和b都指向同一个值,这样就存在问题。如果operator= 是下面这种写法

    class Bitmap {};
    
    class Widget {
    public:
        Widget& operator=(const Widget& rhs){
            delete pb;
            pb = new Bitmap(*rhs.pb);
            return *this;
        }
    private:
        Bitmap* pb;
    }

      如果rhs == this,则delete pb时也删除了rhs中的pb。所以可以改写成

    class Bitmap {};
    
    class Widget {
    public:
        Widget& operator=(const Widget& rhs){
            Bitmap* pOrig = pb;
            pb = new Bitmap(*rhs.pb);
            delete pOrig;
            return *this;
        }
    private:
        Bitmap* pb;
    }

    条款十二:复制对象时勿忘其每一个成分

      copy构造函数和copy assignment函数中不要忘了每一个成员变量,忘记了编译器也不会报错。如

    class Student{
    public: 
        Student (const Student& s):name_(s.name_) {};
    private:
        string name_;
        int age_;
    }

      派生类中的copy构造函数也不要忘了基类中的成员变量,可以直接调用基类的构造函数。

    class Person{
    public:
        Person (const Person& p):name_(p.name_), age_(p.age_) {};
    private:
        string name_;
        int age_;
    }
    
    class Student : public Person {
    public:
        Student (const Student& s) : Person(s), grade_(s.grade_){};
    private:
        int grade_;
    }

    条款十三:以对象管理资源

      在c++中new和delete必须是同时存在的,但很多时候会忘记delete,或者说你很仔细的没有忘记delete,但是在new和delete之间的代码可能会存在return,continue等这类操作,而跳过了delete,为了防止这种内存泄漏的情况发生,所以也就有了以对象管理资源,当对象呗释放时,对象的析构函数会自动释放这些资源,如auto_ptr就是这种的资源管理对象。而c++11中的unique_ptr,shared_ptr也是这一类对象,只不过unique_ptr不能多个对象共享一块内存,而shared_ptr通过引用计数机制,可以多个对象共享一块内存,只有当引用计数为0才会释放内存。

    条款十四:在资源管理类中小心copying行为

      RAII(Resource Acquisition Is Initailization,资源取得时便是初始化时,也是以对象管理资源的概念)对象复制时要一并复制它所管理的资源。而普遍常见的RAII class copying行为是:抑制copying,施行引用计数法等。

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

      APIs中往往需要访问原始资源,所以每一个RAII class应该提供一个”取得其所管理之资源“的方法,如get成员函数可以获得原始指针,重载了指针取值运算符,转换到原始指针并取值。

      对原始资源的访问可能经由显示转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。

    条款十六:成对使用new和delete时要采取相同形式

      如下例子  

    string* stringArray = new string[100];
    ...
    delete stringArray;

      上面的new和delete并不是一个很好的应用,因为new出来的是一个数组,而delete很可能只释放了一个元素,标准的用法是

    string* stringArray = new string[100];
    ...
    delete [] stringArray;

      所以说new和delete,new [] 和delete []。针对此问题要注意typedef的使用,最好不要对数组使用typedef,否则很容易在delete时忘了带[]。

    条款十七:以独立语句将newed对象置入智能指针

      以独立语句将newed对象存储于智能指针内。如果不这样做,一旦异常抛出,有可能导致难以察觉的资源泄漏。如

    int priority();
    void processWidget(std::shared_ptr<Widget> pw, int priority);
    
    //调用
    processWidget(std::shared_ptr<Widget> (new Widget), priority());

      在c++中上述执行的顺序并不严格,可能先执行new Widget,再执行priority(),最后才执行shared_ptr的调用。这样一旦priority()调用报错,就不会发生new Widget的内存泄漏。所以最好独立语句将对象放入智能指针。

    std::shared_ptr<Widget> pw(new Widget);
    processWidget(pw, priority());

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

      总结成一句话就是在设计接口时,尽量简洁明了,约束性强,不容易发生误用。

    条款十九:设计class犹如设计type

    条款二十:宁以pass-by-reference-to-const替换pass-by-value

      在函数的参数传递过程中对于非内置类型对象,尽量以引用或指针传递,推荐引用,为了避免函数中修改传递的对象,可以加上const修饰符。对于非内置类型,引用的传递要比传值高效很多,因为传值的过程中相当于传递副本,是需要调用copy构造函数,而函数执行完之后还会调用析构函数销毁copy的对象,而引用传递不存在这一问题。

    条款二十一:必须返回对象时,别妄想返回其reference

      在函数的返回值时切勿返回reference、pointer,尤其是指向函数中的局部对象,直接返回值即可,虽然这样会耗时(调用构造函数),但至少是正确的。

    条款二十二:将成员变量声明为private

    条款二十三:宁以non-member,non-friend替换member函数

    条款二十四:若所有参数皆需类型转换,请为此采用non-member函数

    条款二十五:考虑写出一个不抛异常的swap函数

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

      定义的变量即使没有被使用,也会存在构造和析构的操作,也就是或会存在构造和析构的成本,所以在定义变量时尽量在使用时定义。

    条款二十七:尽量少做转型动作

      如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。

      如果转型是必要的,试着将他隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放入到他们自己的代码内。

      宁可使用c++风格的新式转型,不要使用旧式转型。

     条款二十八:避免返回handles指向对象内部成分

      对于下面的例子,返回引用(handles,指针,迭代器也可以归为这一类)会导致内部数据的不安全.

    class Point {
    public:
        Point(int x, int y);
        void setX(int newVal);
        void setY(int newVal);
    }
    
    struct RectData{
        Point ulhc;
        Point lrhc;
    }
    
    class Rectangle{
    public:
        Point& upperLeft () const {return pData->ulhc};
        Point& lowerRight () const {return pData->lrhc};
    private:
        std::shared_ptr<RectData> pData;
    }

      上面的Rectangle类中,虽然upperLeft函数是const函数,但是返回的结果是Point的引用,通过该引用是可以修改Point内部的值,包括返回指针和迭代器都是会发生类似的情况,所以如果硬要返回引用这类handles,可以返回const references

    class Rectangle{
    public:
        const Point& upperLeft () const {return pData->ulhc};
        const Point& lowerRight () const {return pData->lrhc};
    private:
        std::shared_ptr<RectData> pData;
    }

    条款二十九:为”异常安全“而努力是值得的

    条款三十:透彻了解inlining的里里外外

      总之只有在小型且频繁被调用的函数身上才使用inlining。这样可以保证代码膨胀的问题最小化,程序的速度提升最大化,如max函数。

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

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

      基类和派生类之间一定要有is-a关系,即基类的属性和方法,在派生类中一定是合理存在的。

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

      只要熟悉作用域,根据作用域去定义变量就可以了,知道在使用变量时作用域的先后顺序。

    条款三十四:区分接口继承和实现继承

      这里涉及到基类中的纯虚函数,虚函数和非虚函数以及继承的概念。

      纯虚函数:含有纯虚函数的类是抽象类,不可以被new出来。对于纯虚函数,子类只继承了接口,子类中必须声明和实现纯虚函数,当然父类中也可以定义纯虚函数,但要指定父类名称才可以调用。

      虚函数:子类会继承虚函数的接口和缺省实现,子类中也可以重写虚函数。

      非虚函数:子类会继承非虚函数的接口和强制实现,不建议子类中重写非虚函数。如果硬要重写,调用时基类指针会调用基类中的函数,想调用子类中的重写函数,必须使用子类指针。

    条款三十五:考虑virtual函数以外的其他选择

      在子类继承父类的功能时,可以引入一些设计模式来使得继承更灵活。如使用模板模式,策略模式。

    条款三十六:绝不重新定义继承而来的非虚函数

      正如上面所说如果子类重写了父类的非虚函数,父类指针即使是指向子类,当调用非虚函数时,还是会调用父类的非虚函数,所以非虚函数的调用和指针类型绑定。所以为了避免出现这样的迷惑行为,还是不要重写非虚函数。

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

      对于继承而来的虚函数或纯虚函数,如果函数中有定义的缺省参数值,继承时不要修改缺省参数值,因为缺省参数值是静态绑定(发生在编译期)的。而虚函数或纯虚函数时动态绑定(发生在运行期)的。

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

    条款三十九:明智而审慎地使用private继承

      private并不会继承接口,而是继承基类的实现,总之尽量使用public,除非特殊实现上,因为private继承本质上不属于is-a关系。

    条款四十:明智而审慎地使用多重继承

      总之能使用单一继承的尽量使用单一继承,多重继承太过复杂,容易引入歧义,且应用于多重继承的虚继承既要提前设计也会引入大小、速度等成本。

    条款四十一:了解隐式接口和编译器多态

      c++中的模板创造了隐式接口和编译期的多态。

    条款四十二:了解typename的双重意义

      在声明模板时,template<typename T> 和 template<class T>的含义是一样的,但是typename可以表示嵌套从属类型,如T::setX到底是一个类型还是成员名称,如果加上typename就很明确是一个类型了,typename T::setX。

  • 相关阅读:
    Wordpress安装及4.6漏洞问题
    天朝挖煤的题已经不会做了。。
    Python str decode---error
    requests库的初级学习
    Python虚拟环境的搭建
    天朝挖煤CTF
    七、TCP粘包和拆包
    六、Netty的Handler
    五、GoogleProtobuf
    三、Netty高性能架构设计
  • 原文地址:https://www.cnblogs.com/jiangxinyang/p/13566023.html
Copyright © 2011-2022 走看看