《effective C++》-第6-9章-学习笔记下

《effective C++》-第6-9章-学习笔记下

[TOC]
本文为《effective C++》学习笔记的下半部分, 涵盖内容第 6-9 章的内容.

继承与面向对象设计

Item32. Make sure public interitance models “is-a”

  • public 继承是一种 is-a 关系, Base 类中的每一件事情都适用于 Derived 类, 因为每一个 Derived 对象都是 Base 对象.

Item33. Avoid hiding inherited names

  • 名称遮掩规则(name-hiding-rules), 会导致 Derived 类里同名的函数遮盖掉 Base 类里所有同名的函数(所有的重载函数).

  • 使用 using 可以使 Base 类中的一个重载系列函数在 Derived 类中可见.

  • 使用 forwarding function 可以实现 Base 类中的一个重载系列函数中的某个具体函数在 Derived 类中可见.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //using
    class Base {
    private:
    int x;
    public:
    virtual void mf1() = 0;
    virtual void mf1(int);
    virtual void mf2();
    void mf3();
    void mf3(double);
    ...};

    class Derived: public Base {
    public:
    using Base::mf1;
    // make all things in Base named mf1 and mf3
    using Base::mf3;
    // visible (and public) in Derived’s scope
    virtual void mf1();
    void mf3();
    void mf4();
    ...
    };

    class Derived2: private Base {
    public:
    virtual void mf1()
    //仅使一个函数可见
    { Base::mf1(); }
    ...
    };

Item34. 区分接口继承与实现继承

  • pure virtual 函数, simple virtual 函数, non-virtual 函数分别对应的意义是, 只继承接口(Base 类必须提供一份实现, 但可以自由发挥实现), 继承接口和一份缺省的实现(Base 类必须提供一份实现, 如果没有特化的实现需求, 可以使用 Base 类的缺省版本), 继承接口和一份强制的实现.

  • simple virtual 函数的缺省版本可能导致问题: 新加入的 Derived 类, 需要特化实现, 如果忘记特化, 还是可以继承缺省的实现导致问题. 解决办法如下:
    提供一个 pure virtual 函数(simple virtual 函数转化)的 protected 影子函数负责 Derived 类里强制特化. 缺点是容易造成命名污染. protected 保护接口不会暴露在用户面前.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Airplane {
    public:
    virtual void fly(const Airport& destination) = 0;
    ...
    protected:
    void defaultFly(const Airport& destination);
    };

    void Airplane::defaultFly(const Airport& destination){
    "default code for flying an airplane to the given destination"}

    class ModelA: public Airplane {
    public:
    virtual void fly(const Airport& destination)
    { defaultFly(destination); }
    ...
    };

    class ModelB: public Airplane {
    public:
    virtual void fly(const Airport& destination)
    { defaultFly(destination); }
    ...
    };

    class ModelC: public Airplane {
    public:
    virtual void fly(const Airport& destination);
    ...
    };

    void ModelC::fly(const Airport& destination){
    "code for flying a ModelC airplane to the given destination"}

Item35. Consider alternatives to virtual functions

  • Non-virtual_interface(NVI) 实现 Template Methond 模式
    non-virtual 成员函数的 wrapper + private virtual 成员函数的实现, 前者决定什么时候实现, 后者决定具体怎么实现.
    缺点是需要即便不使用, Derived 类里也需要在 private 里写出实现函数.

  • Function Pointer 实现 Strategy 模式
    通过调用不同的函数, 跳过继承实现不同的效果. 缺点需要访问 non-public 成员时的限制, 可以通过声明 friend 函数等方式解决.

  • std::function 实现 Strategy 模式
    把函数指针泛化成更一般的函数对象, 通过 std::bind 调用成员函数等方式更加增加了自由度.

Item36. Never redifine an inherited non-virtual function

  • non-virtual 成员函数均为静态绑定(statically bound), 依据指针类型决定执行具体的函数.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class B {
    public:
    void mf();
    ...
    };

    class D: public B { ... };

    D x;
    B *pB = &x;
    pB->mf();// 调用B::mf
    D *pD = &x;
    pD->mf();// 调用D::mf
  • virtual 成员函数均为动态绑定(dynamically bound), 只会依据对象本身类型(动态的, 运行时决定的)决定执行的函数.

Item37. Never redfine a function’s interited default parameter value

  • virtual 成员函数均为动态绑定, 但是缺省参数绑定却是静态绑定, 原因是编译期间决定提升执行效率.

  • 问题描述

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Shape {
    public:
    enum ShapeColor { Red, Green, Blue };
    virtual void draw(ShapeColor color = Red) const = 0;
    ...
    };

    class Rectangle: public Shape {
    public:
    // notice the different default parameter value — bad!
    virtual void draw(ShapeColor color = Green) const;
    ...
    };

    Shape *pr = new Rectangle;
    pr->draw(); //calls Rectangle::draw(Shape::Red)!
  • 解决办法之一: 使用 NVI 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Shape {
    public:
    enum ShapeColor { Red, Green, Blue };
    void draw(ShapeColor color = Red) const{doDraw(color);}
    ...
    private:
    virtual void doDraw(ShapeColor color) const = 0;
    };

    class Rectangle: public Shape {
    public:
    ...
    private:
    virtual void doDraw(ShapeColor color) const;
    ...
    };

Item38. Model a “has-a” or “is-implemented-in-terms-of” through composition

复合(composition)的意义与继承 public 完全不同, 是 “has-a” or “is-implemented-in-terms-of” 的关系.

Item39. Use private inheritance judiciously(明智地)

  • private inheritance 是一种 “is-implemented-in-terms-of”, 优先级比 composition(不同类的组合, 包含)低.
  • 只有排除其他不可行的选项, 并且 Derived 类需要访问 protected Base 成员或者需要重新定义继承而来的 virtual 函数时才是合理的.
  • 通过**空白基类优化(EBO:empty base optimiazation)**可以使继承空白类的类大小最小.
    1
    2
    3
    4
    5
    class HoldsAnInt: private Empty {
    private:
    int x;
    };
    sizeof(HoldsAnInt) == sizeof(int); //几乎为真

Item40. Use multiple interitance judiciously

  • 多重继承比单一继承复杂很多, 会导致新的歧义性.

  • virtual 继承会增加大小, 速度, 初始化(及赋值)的复杂度等成本. 如果 virtual base 类不带任何数据成员变量, 将会是最具有实际价值的情况.

  • 多重继承有其用途, 例如把”public 继承某个 interface” 与 “private 继承某个协助实现的 class” 的两相组合.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    // this class specifies the interface to be implemented
    class IPerson {
    public:
    virtual ~IPerson();
    virtual std::string name() const = 0;
    virtual std::string birthDate() const = 0;
    };

    class DatabaseID { ... };

    // this class has functions useful in implementing the IPerson interface
    class PersonInfo {
    public:
    explicit PersonInfo(DatabaseID pid);
    virtual ~PersonInfo();
    virtual const char * theName() const;
    virtual const char * theBirthDate() const;
    ...
    private:
    virtual const char * valueDelimOpen() const;
    virtual const char * valueDelimClose() const;
    ...
    };

    // note use of MI
    class CPerson: public IPerson, private PersonInfo {
    public:
    explicit CPerson(DatabaseID pid): PersonInfo(pid) {}
    virtual std::string name() const
    { return PersonInfo::theName(); }
    virtual std::string birthDate() const
    { return PersonInfo::theBirthDate(); }
    private:
    const char * valueDelimOpen() const { return ""; }
    const char * valueDelimClose() const { return ""; }};

I40_multiple_inheritance_UML.png

模板与泛形编程

Item41. 了解隐式接口和编译期多态

  • 概念
    从属名称: 名称如果相依于某个 template 参数.
    嵌套从属名称: 从属名称在 class 内成嵌套状.
    嵌套从属类型名称: 嵌套从属名称规定了一个类型.

    1
    2
    3
    4
    5
    6
    7
    template<typename C>
    void print2nd(const C& container)
    {
    if (container.size() >= 2) {
    typename C::const_iterator iter(container.begin());}
    }
    ...
  • Class 使用显式接口(explicit interface), 通过 virtual 函数实现运行时多态(runtime polymorphism), 通过函数重载解析(function overloading resolution)实现 template 使用隐式接口(implicit interface), 编译时多态(compile-time polymorphism).

  • 显式接口依赖于函数签名, 而隐式接口依赖于有效表达式.

Item42. 了解 typename 的双重意义

  • 声明 template 参数时, 前缀 classtypename 可互换.

  • 对嵌套从属类型名称前加上 typename, 但是不能在 base class list 以及member initilization list 内使用 typename.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template<typename T>
    class Derived: public Base<T>::Nested {
    //base class list:typename not allowed
    public:
    explicit Derived(int x): Base<T>::Nested(x)
    //mem. init. list: typename not allowed
    {
    typename Base<T>::Nested temp; //typename必须被使用
    ...
    }
    ...
    };
  • 不声明 typename 的例子, *有可能会被认为是乘号, const_iterator 可能是 C 类里的成员变量.

    1
    C::const_iterator * x;

Item43. 学习处理模板化基类内的名称

  • 当从 OOB C++ 进入 template C++ 时, 继承不一定顺利进行. 因为编译器无法事先知道有没有全特化的类模板.
    全特化(显示具体化(Explicit Specialization))为 template<> 为首行的模板, 只针对一个特定的模板参数.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    class CompanyA {
    public:
    ...
    void sendCleartext(const std::string& msg);//明文信息
    void sendEncrypted(const std::string& msg);//加密信息
    ...
    };

    class CompanyB {
    public:
    ...
    void sendCleartext(const std::string& msg);
    void sendEncrypted(const std::string& msg);
    ...
    };

    class MsgInfo { ... }; //存储 Msg

    template<typename Company>
    class MsgSender {
    public:
    ...
    void sendClear(const MsgInfo& info)
    {
    std::string msg;
    create msg from info;
    }
    Company c;
    c.sendCleartext(msg);
    void sendSecret(const MsgInfo& info)
    { ... }
    };

    //继承MsgSender<Company>增加log功能.
    template<typename Company>
    class LoggingMsgSender: public MsgSender<Company> {
    public:
    ...
    void sendClearMsg(const MsgInfo& info)//无法通过编译
    {
    write "before sending" info to the log;
    sendClear(info);
    write "after sending" info to the log;
    }
    ...
    };

    //因为可能存在全特化的 Z
    class CompanyZ {
    public:
    ...
    void sendEncrypted(const std::string& msg);
    //不存在 sendClear 函数
    //如果在 LoggingMsgSender 里调用 sendClear 函数, 必然错误.
    ...
    };

    template<>
    class MsgSender<CompanyZ> {
    public:
    ...
    void sendSecret(const MsgInfo& info)
    { ... }
    };
  • 解决办法

  1. this->

    1
    this->sendClear(info);
  2. using

    1
    using MsgSender<Company>::sendClear; 
  3. 前置声明

    1
    MsgSender<Company>::sendClear(info);

Item44. 将与参数无关的参数抽离 templates

  • 模板的原本意义就是尽量消除重复代码, 但是如果模板参数个数较多也会导致重复的代码, 导致代码膨胀, 如下, 例如下面对方阵的求逆运算:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template<typename T,std::size_t n>
    class SquareMatrix {
    public:
    ...
    void invert();
    };

    SquareMatrix<double, 5> sm1;
    sm1.invert();
    SquareMatrix<double, 10> sm2;
    sm2.invert();
    //sm1与sm2出现了很多重复的代码
  • 解决方法: 把模板参数分层.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    template<typename T>
    class SquareMatrixBase {
    //基类只需要一个模板参数
    //其余所需要的数据由子类继承后给予(矩阵维度,矩阵内存指针)
    protected:
    SquareMatrixBase(std::size_t n, T *pMem): size(n), pData(pMem) {}
    void setDataPtr(T *ptr) { pData = ptr; }
    void invert(std::size_t size);
    ...
    private:
    std::size_t size;
    T *pData;
    };

    template<typename T, std::size_t n>
    class SquareMatrix: private SquareMatrixBase<T> {
    //private继承
    public:
    //SquareMatrix(): SquareMatrixBase<T>(n, data) {}
    SquareMatrix(): SquareMatrixBase<T>(n, 0), pData(new T[n*n])
    { this->setDataPtr(pData.get()); }
    //将 base class 里的数据指针设置为 null
    //然后 new 出来矩阵再将它的一个副本交给 base class
    void invert() { invert(this->n); }
    //防止遮盖基类, inline 调用基类 invert 函数,成本几乎为0
    ...
    private:
    //T data[n*n];
    boost::scoped_array<T>//heap new 时用
    };
  • 类型参数模板(type template parameter), 即对参数再细化考虑到类型, 例如:

  1. int 与 long 内存一致, 因此 vector<int> 应该与 vector<double> 函数完全一致, 否则导致代码膨胀.
  2. 所有指针应该都为同一种类型, 除了操作强型指针(strongly typed pointer)==void *.

Item45. 运用成员函数模板接受所有兼容类型

  • 问题: 类模板无法对常规隐式转换做出相同的转换. 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template<typename T>
    class SmartPtr {
    public:
    explicit SmartPtr(T *realPtr);
    ...
    };

    SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle); //无法自动隐式转换
    SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom); //无法自动隐式转换
    SmartPtr<const Top> pct2 = pt1;
  • 解决方案: 引入新的模板成员函数处理转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template<typename T>
    class SmartPtr {
    public:
    template<typename U>
    SmartPtr(const SmartPtr<U>& other) : heldPtr(other.get()) { ... }
    //构造函数里完成转换
    T* get() const { return heldPtr; }
    ...
    private:
    T *heldPtr;
    };

    构造函数故意不加 explicit 修饰是为了最大限度地让 C++ 实现隐式转换, 被称为泛化 copy 构造函数.

  • 可转换的类型限制, 使用 explicit 修饰.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    template<class T> class shared_ptr {
    public:
    template<class Y>
    explicit shared_ptr(Y * p);
    template<class Y>
    shared_ptr(shared_ptr<Y> const& r);
    template<class Y>
    explicit shared_ptr(weak_ptr<Y> const& r);
    template<class Y>
    explicit shared_ptr(auto_ptr<Y>& r);
    template<class Y>
    shared_ptr& operator=(shared_ptr<Y> const& r);
    template<class Y>
    shared_ptr& operator=(auto_ptr<Y>& r);
    ...
    };

Item46. 需要类型转换时需要为模板定义非成员函数

  • template 实参推导过程中不会将隐式类型转换函数纳入考虑.

  • 解决办法: 把支持类型转换的函数作为友元函数放入 template 中, 同时注意把实现也放在一起防止无法找到友元函数, 无法通过编译.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    template<typename T>
    class Rational {
    public:
    Rational(const T& numerator = 0, const T& denominator = 1);
    const T numerator() const;
    const T denominator() const;
    friend
    const Rational operator*(const Rational& lhs, const Rational& rhs){
    return Rational(lhs.numerator() * rhs.numerator(),
    lhs.denominator() * rhs.denominator());}
    ...
    };

    template<typename T>
    const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs)
    { ... }
  • 如果友元函数的实现很复杂, 可以通过调用 helper 函数实现.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    friend
    const Rational<T> operator*(const Rational<T>& lhs,const rational<T>& rhs)
    { return doMultiply(lhs, rhs); }

    template<typename T>
    const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs) {
    return Rational<T>(lhs.numerator() * rhs.numerator(),
    lhs.denominator() * rhs.denominator());
    }

Item47. 使用 traits classes表现类型信息

如何实现 STL 的 advance template 函数?

  • STL 迭代器分类, 五种分类分别提供专属的卷标结构(tag struct):

    input 迭代器, one-pass algorithm, 模仿 read pointer, 一次只能向前一步.

    output 迭代器, one-pass algorithm, 模仿 write pointer, 一次只能向前一步.

    forward 迭代器, multi-pass algorithm, 一次能向前 N 步.

    bidirectional 迭代器, 双向多步移动.

    random 迭代器, 双向多步移动+迭代器算数.

    1
    2
    3
    4
    5
    struct input_iterator_tag {};
    struct output_iterator_tag {};
    struct forward_iterator_tag: public input_iterator_tag {}; //public继承关系
    struct bidirectional_iterator_tag: public forward_iterator_tag {};
    struct random_access_iterator_tag: public bidirectional_iterator_tag {};
  • traits 介绍: 不是关键字或者预先定义好的构件, 而是 C++ 程序员共同遵守的协议, 要求对内置类型要和用户自定义类型的表现必须一样好.

    因为要对内置类型有效, traits 信息只能放在类型自身之外, 标准技术将其放入一个 template 及其一个或多个特化版本中.

    迭代器的 traits: iterator_traits.

  • iterator_traits 的实现, 首先每一个用户自定义的迭代器类型必须嵌套一个 typedef, 命名为 iterator_category, 用来确认卷标结构(tag struct). 然后 iterator_traits 里模板判断具体什么类型即可.

    对于指针(用于实现随机访问迭代器)提供**偏特化(partial template specilization)**版本.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    template < ... > // template params elided
    class deque {
    public:
    class iterator {
    public:
    typedef random_access_iterator_tag iterator_category;
    //定义 iterator_category
    ...
    };
    ...
    };

    template < ... >
    class list {
    public:
    class iterator {
    public:
    typedef bidirectional_iterator_tag iterator_category;
    //定义iterator_category
    ...
    };
    ...
    };

    template<typename IterT>
    struct iterator_traits {
    typedef typename IterT::iterator_category iterator_category;
    //通过模板参数确定卷标结构
    ...
    };

    template<typename IterT>
    struct iterator_traits<IterT *> { //指针偏特化版本
    typedef typename random_access_iterator_tag iterator_category;
    ...
    };
  • 通过重载而不是 if else 判断, 使得能够在编译期间就选对正确的卷标结构, 提高执行效率与缩减二进制文件大小. 此模式为下一节的 TMP 编程.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    template<typename IterT, typename DistT> // use this impl for random access iterators
    void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag)
    { iter += d;}

    template<typename IterT, typename DistT> // use this impl for bidirectional iterators
    void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag) {
    if (d >= 0) { while (d--) ++iter; }
    else { while (d++) --iter; }
    }

    template<typename IterT, typename DistT> // use this impl for input iterators
    void doAdvance(IterT& iter, DistT d, std::input_iterator_tag){
    if (d < 0 ) {
    throw std::out_of_range("Negative distance");
    }
    while (d--) ++iter;
    }

    template<typename IterT, typename DistT>
    void advance(IterT& iter, DistT d){
    doAdvance( iter, d, typename std::iterator_traits<IterT>::iterator_category() );
    }

Item48. Be aware of template metaprogramming

  • TMP(template metaprogramming) 是以 C++ 写成, 执行于编译器内的程序, 是图灵完备(Turing-complete)机器.

  • 好处: 使某些操作更容易, 运行期转移至编译期变得高效. 坏处, 编译时间变长了.

  • 库: Boost::MPL

  • 例子, 递归, 递归模板具现化(recursive template instantiation).

    1
    2
    3
    4
    5
    6
    7
    8
    template<unsigned n> 
    struct Factorial {
    enum { value = n * Factorial<n-1>::value };
    };
    template<>
    struct Factorial<0> {
    enum { value = 1 };
    };
  • TMP 使用场景

    1. 编译检查–确保度量单位正确的正确结合.
    2. 优化矩阵运算, expression template.
    3. 生成客户定制的设计模式, policy-based design 之 TMP-based 技术, 泛化为 generatige programming.

定制 newdelete

STL 容器的 heap 内存是由容器所拥有的分配器对象(allacator objects)管理的,不适应于 new/delete, 为定制的 newdelete.

Item49. Understand the behavior of the new-handler

  • new-handler, operator new 抛出异常反馈一个未获满足的内存需求之前会去调用一个客户指定的错误处理函数, 即为 new-handler 函数.

  • set_new_handler 为 new-handler 的接口函数. new_handler 为一个函数指针.

    1
    2
    3
    4
    namespace std {
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();
    }
  • operator new 无法满足内存申请时, 它会不断调用 new-handler 函数, 直到找到足够的内存. 因此 new-handler 函数需要满足如下条件:

  1. 让更多的内存可以被使用.
    例如, 程序一开始申请一大块内存, 当 new-handler 函数第一次被调用将内存释放还给程序使用.
  2. 安装另一个 new-handler 函数
    如果这次找不到足够的内存, 也许它知道别的 new-handler 函数可以做到, 需要可以被替换(调用 set_new_handler).
    可以通过改变 global 性质的数据(static, namespace 内, global 变量等)改变 new-handler 函数的状态以去明白调用什么样的新的 new-handler 函数.
  3. 卸除 new-handler 函数
    设置 new-handler 函数为 null/0.
  4. 抛出 bad_alloc 的异常
  5. 不返回, 通常返回 abort/exit
  • 类专属 new-handler 函数的创建, 依赖于复合思路(composition)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Widget {
    public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    private:
    static std::new_handler currentHandler;
    };
    std::new_handler Widget::currentHandler = 0; //非 const 整数 static 成员变量必须在声明外定义. 目前指向 null.

    std::new_handler Widget::set_new_handler(std::new_handler p) throw(){
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
    }//这里是一个顿错, 先试一下当前的 new-handler, 失败的情况下换新装的 new-handler.

依据上述设定 operator new 需要以下事情:

  1. 先安装 gloabl new-handler.
  2. operator new 如果分配失败, 重新安装为 Widget 的 new-handler, 同时保证 gloabl new-handler 的资源安全性.
  3. 如果分配成功, operator new 返回分配的指针. 析构 gloabl new-handler, 并把 Widget 的 new-handler 安装上.
  • 上面的做法不够 fashion, 使用资源处理类包装 new-handler .

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class NewHandlerHolder {
    public:
    explicit NewHandlerHolder(std::new_handler nh): handler(nh) {}//设置为新的 new-handler
    ~NewHandlerHolder(){ std::set_new_handler(handler); } //复原 new-handler
    private:
    std::new_handler handler;
    NewHandlerHolder(const NewHandlerHolder&);
    NewHandlerHolder& operator=(const NewHandlerHolder&);
    };

    void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
    {
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);
    }

    void outOfMem();{ //自定义的 new-handler
    std::cerr << "Unable to satisfy request for memory\n"; //较真的话,std::cerr 也有可能出错.
    std::abort();}


    Widget::set_new_handler(outOfMem); //设置为自定义的

    Widget *pw1 = new Widget; //如果分配失败调用 outOfMem

    std::string *ps = new std::string; //如果分配失败调用 global new-handling 函数

    Widget::set_new_handler(0); //设置自定义的 new-handling 为0

    Widget *pw2 = new Widget; //因为没有 new-handling ,分配失败立即报错.
  • 还是不够 fashion, 抛弃复合 composition 思路, 使用类继承的思路, 即具体的类去继承一个自定义 new-handling 的 base 类.
    这里使用的是带有自身类为模板参数的模板基类: CRTP(curiously recurring template pattern).

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // "mixin-style" base class for class-specific set_new_handler support
    template<typename T>
    class NewHandlerSupport {
    public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ...
    private:
    static std::new_handler currentHandler;
    };

    template<typename T>
    std::new_handler
    NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw(){
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;}

    template<typename T>
    void* NewHandlerSupport<T>::operator new(std::size_t size)
    throw(std::bad_alloc){
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);}

    // this initializes each currentHandler to null
    template<typename T>
    std::new_handler NewHandlerSupport<T>::currentHandler = 0;

    //public继承
    class Widget: public NewHandlerSupport<Widget> {
    ...
    };

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

  1. 为了检测运用错误
    例如为了排查代码原因导致的数据 overrun/underrun(写入点在分配区块尾端之后/首端之前), 可以自定义 operator new, 分配额外更多的内存, 然后放入特定的 Byte Pattern(signatures), 然后与实际写入数据比对就可以发现到底是 overrun 还是 underrun 了.
  2. 为了强化效能
    定制版比通用版在自定义下的场景效率更高.
  • 增加分配归还速度: 例如 Boost 中的 Pool 库可以加速区块尺寸固定的对象.
  • 降低缺省内存管理器带来的内存开销.
  • 弥补缺省内存管理器的非齐位(suboptimal allignment).
  • 将相关对象集束成页, 减少 paging 成本.
  • 获取非传统行为, 例如管理共享内存的分配与归还.
  1. 收集使用上的统计数据

Item 51: Adhere to convention when writing new and delete.

  • pseudocode for a non-member operator new:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void* operator new(std::size_t size) throw(std::bad_alloc){
    using namespace std;
    if (size == 0) {size = 1;} //此处避免分配为空的问题
    while (true) { //此处为循环,直到失败时 new_handling 没有可用的新 new_handling 可以调用.
    attempt to allocate size bytes;
    if ( the allocation was successful)
    return (a pointer to the memory);//分配成功返回内存指针
    new_handler globalHandler = set_new_handler(0);//分配失败,使用定制的 new_handling
    set_new_handler(globalHandler);
    //定制的也不行,要么接着调用其他 new_handling,要么抛出异常
    if (globalHandler) (*globalHandler)();
    else throw std::bad_alloc();
    }}
  • 针对自定义类的区块大小, 通过类继承的方式.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Base {
    public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ...
    };
    class Derived: public Base
    { ... };
    Derived *p = new Derived;

    void* Base::operator new(std::size_t size) throw(std::bad_alloc){
    if (size != sizeof(Base))
    return ::operator new(size);}
  • 析构同样需要考虑不能 delete 空指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Base {
    public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    static void operator delete(void *rawMemory, std::size_t size) throw();
    ...
    };

    void Base::operator delete(void *rawMemory, std::size_t size) throw()
    {
    if (rawMemory == 0) return;
    if (size != sizeof(Base)) {
    ::operator delete(rawMemory);
    return;}
    }

    deallocate the memory pointed to by rawMemory;
    return;

Item 52: Write placement delete if you write placement new.

  • placement operator new/delete 是指自定义的 operator new/delete. 会引发 2 个问题, 一是 placement operator new 与 placement operator delete 要成对使用, 否则无法析构成功. 二是注意 placement operator new/delete 对 global 版本的遮蔽.
    需要引入头文件<new>.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    class Widget {
    public:
    ...
    static void* operator new(std::size_t size, std::ostream& logStream)
    throw(std::bad_alloc);
    static void operator delete(void *pMemory, std::size_t size) throw();
    };

    class Derived: public Widget {
    public:
    ...
    static void* operator new(std::size_t size)
    throw(std::bad_alloc);
    ...
    };

    Derived *pd = new (std::clog) Derived;// error! Base’s placement new is hidden
    Derived *pd = new Derived;// fine, calls Derived’s operator new

    //把 global 的 new/delete 封装成类让placement new/delete 的类去继承
    class StandardNewDeleteForms {
    public:
    // normal new/delete
    static void* operator new(std::size_t size) throw(std::bad_alloc)
    { return ::operator new(size); }
    static void operator delete(void *pMemory) throw()
    { ::operator delete(pMemory); }
    // placement new/delete
    static void* operator new(std::size_t size, void *ptr) throw()
    { return ::operator new(size, ptr); }
    static void operator delete(void *pMemory, void *ptr) throw()
    { return ::operator delete(pMemory, ptr); }
    // nothrow new/delete
    static void* operator new(std::size_t size, const std::nothrow_t& nt) throw()
    { return ::operator new(size, nt); }
    static void operator delete(void *pMemory, const std::nothrow_t&) throw()
    { ::operator delete(pMemory); }
    };

    class Widget: public StandardNewDeleteForms {
    public:
    // inherit std forms make those forms visible
    using StandardNewDeleteForms::operator new;
    using StandardNewDeleteForms::operator delete;
    // add a custom placement new
    static void* operator new(std::size_t size, std::ostream& logStream)
    throw(std::bad_alloc);
    // add the corresponding placement delete
    static void operator delete(void *pMemory,
    std::ostream& logStream)
    throw();
    ...
    };

杂项

Item53. Pay attetion to compiler warning

  • 注意warning,也要注意warning本身依赖于编译器平台.

Item54. Familiarize yourself with the standard library, including TR1

  • STL
  • Iostream
  • 国际化支持:wchar_t, wstring,Unicode
  • 数值处理: 复数模板(complex),纯数值数组(valarray)
  • 异常阶层体系(exception hierarchy)
  • C89标准库

TR1标准库内容:

  • 智能指针: shared_ptr,weak_ptr(环形数据)
  • function
  • bind
  • hash_table
  • 正则表达式
  • 元组tuple
  • arrary
  • mem_fun/mem_fun_ref
  • reference_wrapper
  • 随机数
  • 数学特殊函数
  • C99兼容扩充
  • type traits
  • result_of template
    上面好多东西都已经成标准了,作者啥时候第四版啊.

Item55. Familiarize yourself with Boost

puplic peer review机制
同样地,求作者出第四版更新本节.

《effective C++》-第6-9章-学习笔记下

https://www.chuxin911.com/effective_c++_2_20211123/

作者

cx

发布于

2021-11-23

更新于

2022-11-23

许可协议