• Effective C++总结


    Effective C++ 3rd 总结

    Effective C++ 3rd 总结

    如果你还没有读过Effective C++,请绕行。如果你读过,可以借鉴下。

    Table of Contents

    1 DONE 1. View C++ as a federation of languages

    • C
    • Object-oriented C++ 面向对象3个特性:封装,继承,多态
    • Template C++
    • STL

    2 DONE 2. Prefer consts, enums and inlines to #defines

    • 一般常量可用const
    • 整型常量可用enum,与define类似,不可取地址
    • inline代替#define的函数

    3 DONE 3. Use const whatever possible

    • 防止出现如: a*b=c,对operator*返回const
    • const成员函数2个好处:可知哪个函数可改对象内容,哪个不行;使操作const对象成为可能;
    • 两个函数只是常量性不同可以重载
    • mutable修饰的变量,在const成员函数中可被修改
    • 让non-const函数调用他的const兄弟函数,可减少代码量,用constcast可去除常量性。如下代码:
      char& operator[](…) {
          return const_cast<char&>(
              static_cast<const CDemo&>(*this)[…]
          );
      }
      

    4 DONE 4. Make sure that objects are initialized before they are used

    • 对内置对象类型进行手工初始化
    • 使用成员初始列
    • 用本地静态对象替换非本地静态对象,为免除跨编译单元的初始化次序问题 CDemo& demo() { static CDemo demo; return demo; } … demo().foo();

    5 DONE 5. Know what functions C++ silently writes and calls

    • default构造函数
    • 析构函数 主要调用基类和non-static成员变量的构造函数和析构函数
    • copy构造函数
    • assignment操作符 对源对象所有non-static成员变量进行拷贝 若类中含有const或reference,它们是禁止随意修改的,默认生成的拷贝构造函数和赋值运算符就会出错。 若基类声明拷贝构造函数为private,派生类就不会自动生成拷贝构造函数。

    6 DONE 6. Explicitly disallow the use of compiler-generated functions you do not want

    • 可将其声明为private且没有实现部分。

    7 DONE 7. Declare destructors virtual in polymorphic base clssses

    • 带有多态性质(即通过基类接口处理派生类)的基类应声明一个virtual析构函数

    8 DONE 8. Prevent exceptions from leaving destructors

    • 在析构函数中要呑下异常,或结束程序
    • 防止资源泄露

    9 DONE 9. Never call virtual functions during construction or destruction

    • 构造基类成分时,若调用virtual函数,此时派生成分尚未构造

    10 DONE 10. Have assignment operators return a reference to *this

    a=b=c=10; // 诸如= += -= …的赋值操作符,令其返回一个*this的引用
    CDemo& operator=(CDemo &rhs)
    {
        …
        return *this;
    }
    

    11 DONE 11. Handle assignment to self in operator=

    • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确
    • 确保自我赋值成功。如a=a; 如下自我赋值时会失败:
    CDemo& CDemo::operator=(const CDemo& rhs)
    {
        delete m_pBmp;
        m_pBmp = new Bitmap(*rhs.m_pBmp);
        return *this;
    }
    // 解决方案1:
    CDemo& CDemo::operator=(const CDemo& rhs)
    {
        if (this == &rhs) return *this; // 加了此句判断
        delete m_pBmp;
        m_pBmp = new Bitmap(*rhs.m_pBmp); // 缺点:无法保证此句无异常
        return *this;
    } 
    解决方案2:
    CDemo& CDemo::operator=(CDemo rhs) // pass-by-value
    {
        swap(rhs); // 与this调换
        return *this;
    }
    

    12 DONE 12. Copy all parts of an object

    • 拷贝构造函数或是赋值操作符应该: 复制所有local成员变量 调用所有基类的拷贝或是赋值操作
    • 不要以某个copying函数实现另一个copying函数,应将共同机能放进第三个函数中调用。

    13 DONE 13. Use objects to manage resources

    // 存在问题的代码
    void foo()
    {
        CDemo* pDemo = new CDemo();
        … // 若此区间代码过早return,将无法执行delete释放资源的
        delete pDemo;
    }
    

    解决方案:如autoptr和sharedptr。注:这两个类中的析构函数中,做的是delete而不是delete[]操作。意味着在动态分配而得的array身上使用它们是个馊主意。

    14 DONE 14. Think carefully about copying behavior in resource-managing classes

    • 一个RAII(Resource Acquisition Is Initialization)对象被复制,会发生什么? 我们可以做的:
      • 禁止复制,像条歀6中所述。
      • 引用计数,sharedptr便是此法。
      • 转移拥有权,确保永远只有一个RAII对象指向原始资源,autoptr便是此法。

    15 DONE 15. Provide access to raw resources in resource-managing classes

    • 显示转换:sharedptr和autoptr都提供了get()成员函数,用于返回原始指针。
      CDemo* get() const { return m_ptr; }
      
    • 隐式转换:
      operator CDemo*() const { return m_ptr; }
      CDemo* p = my_ptr;
      

    16 DONE 16. Use the same form in corresponding uses of new and delete

    • new和delete,new[]和delete[]要成对使用

    17 DONE 17. Store newed objects in smart pointers in standalone statements

    // 如下可能泄露资源
    process(std::shared_ptr<CDemo>(new CDemo), foo());
    // 可能会得到以下的执行次序
    // 1. 执行 new CDemo
    // 2. 调用foo()
    // 3. 调用std::shared_ptr的构造函数
    // 若调用foo()导致异常,则可能引发资源泄露
    
    • 解决方案:分离语句
      std::shared_ptr<CDemo> pd(new CDemo);
      process(pd, foo());
      

    18 DONE 18. Make interfaces easy to use correctly and hard to use incorrectly

    // 下面的接口看似很好,却很易被误用
    class Date {
    public:
        Date(int month, int day, int year);
        …
    };
    Date d1(30, 3, 1995); // 错
    Date d2(2, 30, 1995); // 错
    

    解决方案:

    struct Day {
        explicit Day(int d) : val(d) {}
        int val;
    };
    // 形如Day,再分别定义Month和Year
    …
    class Date {
    public:
        Date(const Month& m, const Day& d, const Year& y);
        …
    };
    …
    Date d(Month(3), Day(30), Year(1995)); // 有了正确的类型,还需要限制其值,见下一条解决方案
    

    解决方案:限制类型内什么事可做,什么事不可做

    class Month {
    public:
        Month Jan() {return Month(1);}
        Month Feb() {return Month(2);}
        …
    private:
        explicit Month(int m); // 阻止生成新的月份
        …
    };
    Date d(Month::Jan(), …);
    
    • 为避免资源泄露,可使函数返回的指针为智能指针 sharedptr&lt;CDemo> CreateDemo();
    • sharedptr支持定制型删除器 shareptr&lt;CDemo> d(p, MyDeleter);

    19 DONE 19. Treat class design as type design

     

    20 DONE 20. Prefer pass-by-reference-to-const to pass-by-value

    • pass-by-value会导致copy构造函数和析构函数的层层调用。
    • 尽量以pass-by-reference-to-const替换pass-by-value,效率较高,且可避免切割问题(即一个drived class对象以by-value传递并被视为一个base class对象,此时只有base class对象的copy构造函数被调用,切割掉了它做为drived class的部分)
      class Window {
      …
      };
      class WindowWithScrollBars : public Window {
      …
      };
      void foo(Window w) {…}
      …
      WindowWithScrollBars wwsb;
      foo(wwsb); // 造成wwsb只是一个Window对象,派生性质被切除
      
    • 不适用于内置类型,以及STL迭代器和函数对象

    21 DONE 21. Do't try to return a reference when you must return an object

    考虑如下代码: CInt x, y, z, w; w = x * y * z; 考虑operator*(const CInt& lhs, const CInt& rhs)的返回值问题:

    • 返回函数体内local对象的reference 不行,因为local在函数退出前被销毁了
    • 返回static对象的reference 不行,因为if ((a*b) == (c*d))永远是true
    • 返回pointer 不行,谁来销毁它?

    解决方案:return value;

    22 DONE 22. Declare data members private

    • 可以实现对成员变量的访问控制
    • 增强封装性 若public成员变量改变,大量代码要重写 若protected成员变量改变,大量继承自它的类要重写 若private成员变量改变,客户要重写的代码很少
    • 从封装角度看,只有两种访问权限:private(提供封装)和其它(不提供封装),protected和public一样,不具封装性

    23 DONE 23. Prefer non-member non-friend functions to member functions

    • friends函数对private成员的访问权力和member函数相同,从封装的角度看,选择不是在member和non-member函数之间,而是在member和non-member non-friend函数之间
    • 可以将这些non-member non-friend函数放进不同头文件的同一个命名空间内,意味用户可以轻松扩展这些函数

    24 DONE 24. Declare non-member functions when type conversions should apply to all parameters

    class CInt {
    public:
        CInt(int n=0);
        const CInt operator*(const CInt& rhs) const;
    };
    …
    CInt x, y;
    x = y * 2; // ok, x=y.operator*(2)
    x = 2 * y; // 错误, 
    

    解决方案:non-member函数

    const CInt operator*(const CInt& lhs, const CInt&rhs)
    {
        …
    }
    …
    x = 2 * y; // ok !
    
    • 若某个函数的所有参数(包括隐含的this指针)都要进行类型转换时,这个函数必须是non-member

    25 DONE 25. Consider support for a non-throwing swap

    • 标准库的swap
      namespace std {
          template<typename T>
          void swap(T& a, T& b)
          {
              T temp(a);
              a = b;
              b = temp;
          }
      }
      
    • 当缺省的swap无法满足效率时,考虑如下:
      class CDemo {
      private:
          char* m_pStr;
      public:
          void swap(const CDemo& other) {
              using std::swap;
              swap(m_pStr, other.m_pStr);
          }
      };
      namespace std {
          template<>
          swap<CDemo>(const CDemo& a, const CDemo& b) {
              a.swap(b);
          }
      }
      

    26 DONE 26. Postpone variable definitions as long as possible

     

    27 DONE 27. Minimize casting

    • C++提供4种新式转型:constcast, dynamiccast(唯一无法由旧式语法执行的动作,由基类转成派生类), reinterpretcast, staticcast
    • 单一对角可能拥有1个以上地址
      class Base {…};
      class Derived : public Base {…};
      Derived d;
      Base* pb = &d; // 不同编译器,对象布局方式不同,可能导致这两个地址不同,有个偏移量
      
    • 避免不必要的转型
      class CWindow {
      public:
          virtual void OnResize() {…}
      };
      class CSpecialWindow : public CWindow {
      public:
          virtual void OnResize() {
              static_cast<CWindow>(*this).OnResize();
              // 错误,上面调用的只是一个复本
              // 解决方案
              // CWindow::OnResize();
          }
      };
      

    28 DONE 28. Avoid returning "handles" to object internals

    • handles 包括reference, pointers, iterators, 返回它们会降低封装性

    29 DONE 29. Strive for exception-safe code

    • 不泄露资源,不允许数据败坏
    • 函数成功,则彻底成功。失败则回到调用前状态。
    • 不好的例子:
      class CPrettyMenu {
      private:
          Mutex mutex;
          Image* bgImage;
          int imageChanges;
      public:
          void ChangeBackground(std::istream& imgSrc) {
              lock(&mutex); // 没有用对象管理资源
              delete bgImage; // 应该新建成功以后再delete
              ++ImageChanges;
              bgImage = new Image(imgSrc); // 可能抛出异常
              unlock(&mutex);
          }
      };
      
    • 解决方案:
      class CPrettyMenu {
          …
          std::shared_ptr<Image> bgImage;
          …
          void ChangeBackground(…) {
              Lock m1(&mutex);
              bgImage.reset(new Image(imgSrc));
              ++imageChanges;
          }
      };
      
      

    30 DONE 30. Understand the ins and outs of inlining

    • inline一个函数是对此函数的每次调用,都用函数本体去替换,可能导致代码膨胀
    • class中定义的函数,都隐喻有inline

    31 DONE 31. Minimize compilation dependencies between files

     

    32 DONE 32. Make sure public inheritance models "is-a"

    -Derived class "is a" Base class

    33 DONE 33. Avoid hiding inherited names

    • 如果你继承base class并加上重载函数,而你又希望重新定义或复写其中一部分,那么你必须为那些原本被遮掩的每个名称引入一个using声明式,否则某些你希望继承的名称会被遮掩
    • 有时你并不想继承base class的所有函数,但在public继承下,这绝不可能发生,因为违反is-a关系

    34 DONE 34. Differentiate between inheritance of interface and inheritance of Implementation

    • pure virtual目的:只继承函数接口
    • impure virtual目的: 继承接口和缺省实现
    • non-virtual目的:继承接口和强制实现

    35 DONE 35. Consider alternatives to virtual function

    讨论如下的一种替换方案

    class CDemo {
    public:
        virtual void fun();
    };
    

    35.1 NVI手法(Non-Virtual-Interface)

    virtual函数问题被定义为private,由public non-virtual函数去调用它。

    class CDemo {
    private:
        virtual int _foo() {…}
    public:
        int fun() {
            … // 这种手法的优点就是可以在调用前后做一些额外工作。
            … // 调用前准备…
            __foo(…);
            … // 调用后清理
        }
    };
    

    35.2 Function Pointers实现Strategy

    int DefaultFunc(const CDemo& demo) {…} // 缺点是无法访问private成员
    class CDemo {
    public:
        typedef int (*Fun)();
        explicit CDemo(Fun fp = DefaultFunc) : m_fp(fp) {…}
        void fun() {
            return m_fp(*this);
        }
    private:
        Fun m_fp;
    };
    

    35.3 tr1::function实现Strategy

    int DefaultFunc(const CDemo& demo) {…}
    class CDemo {
    public:
        // tr1::function实现兼容:只要返回值可隐式转为int,参数可隐式转为CDemo的引用。都可以
        typedef std::tr1::function<int (const CDemo& demo)> MyFunc;
        explicit CDemo(MyFunc fp = DefaultFunc) : m_fp(fp) {…}
    private:
        MyFunc m_fp;
    };
    
    • tr1::bind
    • tr1::function对象的形为就像函数指针,只是它可接纳所有与目标签名式相兼容的所有可调用物

    35.4 古典Strategy模式

    class CFuncBase { virtual int func(); }; // 其它函数类均继承自CFuncBase class CDemo { public: … private: CFuncBase* mpFunBase; };

    36 DONE 36. Never redefine an inherited non-virtual function

    // 若违反本条规格
    Base *pb = new Derived;
    pb->foo(); // 不论pb指向什么对象,它永远调用的是Base::foo()
    

    37 DONE 37. Never redefine a function's inherited default parameter value

    // 考虑以下不好的代码
    class CBase {
    public:
        virtual void foo(int i = 10) {…}
    };
    class CDerived : public CBase } {
    public:
        virtual void foo(int i = 20) {…} // 默认参数不一样,不好的形为
    };
    CBase* pb = new CDerived;
    pb->foo(); // CDerived::foo(i = 10),默认参数改变了,可能引起未知错误
    

    解决方案:形如NVI手法,若只是单纯的将继承来的函数参数默认值改为一样,一旦基类变更,所有的都要重写

    class CBase {
    public:
        void foo(int i = 10) {
            _foo(i);
        }
    private:
        virtual void _foo(int i) {…}
    };
    

    38 DONE 38. Model "has-a" or "is-implemented-in-terms-of" through composition

    • has-a 通过类的复合,使得class CStudent has-a CName
    • is-implemented-in-terms-of 容器配接器,如stack is-implemented-in-terms-of list

    39 DONE 39. Use private inheritance judiciously

    • private继承意味着is-implemented-in-terms-of,它和复合如何取舍
    • 尽可能使用复合,必要时才使用private继承(如有protected或virtual成员时,以及空类最优化)

    40 DONE 40. Use multiple inheritance judiciously

    • 容易出现歧义(两个base class里都有同一个名字)
    • 钻石型多重继承
    • 若非必要,不用virtual继承,使用时,避免在virtual base class中放置数据

    41 DONE 41. Understand implicit interfaces and compile-time polymorphism

    • 面向对象:explicit interfaces和runtime polymorphism 接口:函数签名 多态:virtual
    • 泛型编程:implicit interfaces和compile-time polymorphism 接口:基于有效表达式 具现化

    42 DONE 42. Understand two meanings of typename

    • template<typename T>…
    • typename T::valuetype n; 不能用于base class lists或member initialization list

    43 DONE 43. Know how to access names in templatized base classes

    • 基类模版可能存在被特化,所以C++拒绝从基类中查找继承而来的名字。
    • 解决方案:三种 调用时加上 "this->" 使用using声明式 明白指出: CBase<int>::foo();

    44 DONE 44. Factor parameter-independent code out of templates

    template<typename T, std::size_t n>
    class CDemo {
    public:
        void foo();
    };
    // 考虑如下这些调用
    CDemo<int, 5> d1;
    CDemo<int, 10> d2;
    d1.foo();
    d2.foo();
    // 这会具现化两份foo(), 引出代码膨胀
    

    45 DONE 45. Use member function templates to accept "all compatible types"

    template<class T> class CDemo { public: template<class U> CDemo(const CDemo<U>& other); template<class U> CDemo& operator=(CDemo<U>& other); };

    • 声明泛化的copy构造函数和assignment操作符,还是需要声明正常的copy构造函数和assignment操作符

    46 DONE 46. Define non-member functions inside templates when type conversions are desired

    • 当编写一个类模板时,它所提供的所有与此模板相关的函数支持所有参数的隐式类型转换时,请将函数定义为类模板内部的friend函数

    47 47. Use traits classes for information about types

     

    47.1 STL的5种iterator

    struct inputiteratortag {}; struct outputiteratortag {}; struct forwarditeratortag : public inputiteratortag {}; struct bidirectionaliteratortag : public forwarditeratortag {}; struct randomaccessiteratortag : public bidirectionaltag {};

    48 48. Be aware of template metaprogramming

     

    48.1 可将工作由运行期移到编译器

     

    49 49. Understand the behavior of the new-handler

     

    49.1 operator new无法满足内存申请时,会不断调用new-handler函数

    typedef void (*newhandler) (); newhandler setnewhandler(newhandler p) throw();

    49.2 良好的new-handler应具备:

    • 让更多内存可被使用
    • 若仍无法成功,可以安装另一个new-handler以替换自己
    • 卸除new-handler,即setnewhandler(0);
    • 抛出badalloc异常
    • 不返回,通常调用abort或exit

    50 50. Understand when it makes sense to replace new and delete

    • 齐位(alignment) 可获得较佳的硬件效率

    51 51. Adhere to conversion when writing new and delete

     

    51.1 operator new

    • 可处理0字节申请
    • 内含一个无限循环,尝试分配内存

    51.2 operator delete

    • 接收null指针时不做任何事

    51.3 考虑如下代码

    class Base { public: // Base类专属operator new static void* operator new(std::sizet size) throw(std::badalloc); … }; class Derived : public Base {…}; Derived* p = new Derived; // 调用的是Base::operator new,可能出错 // 解决方案: void* Base::operator new(std::sizet size) throw(std::badalloc); { if (sizeof(Base) != size) return std::operator new(size); … };

    52 52. Write placement delete if you write placement new

     

    52.1 new operator做2件事

    • 调用operator new
    • 调用构造函数

    52.1.1 若第一步成功,第二步构造时失败,运行期系统会寻找与第一步中operator new参数完全相同的operator delete并调用

     

    52.1.2 若operator new 没有与之参数相同的operator delete,则内存分配需要取消时,不会有operator delete被调用

     

    52.2 缺省情况下C++在全局作用域提供以下3个operator new

    • void* operator new(std::sizet) throw(std::badalloc); // normal new
    • void* operator new(std::sizet, void*) throw(); // placement new
    • void* operator new(std::sizet, const std::nothrowt&amp;) throw(); // nothrow new

    52.2.1 不要无意识的遮掩这些正常版本

     

    53 53. Pay attention to compiler warnings

     

    54 54. Familiarize yourself with the standard library, including TR1

     

    55 55. Familiarize yourself with Boost

     

    Author: rookie2 <yao@yao-Satellite-L800>

    Date: 2013-03-13 15:45:53 CST

    HTML generated by org-mode 6.33x in emacs 23

  • 相关阅读:
    网络安全法课程上线喽~
    网络安全法将在六一正式实施,我该如何继续爱你?
    安卓逆向入门(一)
    如何正确的使用Ubuntu以及安装常用的渗透工具集.
    Linux/CentOS各种服务框架的搭建完整流程
    【Anroid界面实现】WindowManager类使用具体解释——用户首次打开APP的使用教学蒙板效果实现
    2014年辛星jquery解读第三节 Ajax
    迅为4412开发板Linux驱动教程/硬件知识及原理图的使用
    点评国内各大在线app生成平台
    OpenGL ES2.0 基本编程
  • 原文地址:https://www.cnblogs.com/rookie2/p/2957773.html
Copyright © 2020-2023  润新知