【读书笔记】【Effective C++】模板与泛型编程

条款 41:了解隐式接口和编译期多态

  • 面向对象的世界总是以显示接口和运行期多态解决问题:【class 支持显示接口和运行期多态】

    class Widget {
    public:
        Widget();
        virtual ~Widget();
        virtual std::size_t size() const;
        virtual void normalize();
        void swap(Widget& other);
        ...
    };
    
    void doProcessing(Widget& w)
    {
        if( w.size() > 10 && w != someNastyWidget )  {
            Widget temp(w);
            temp.normalize();
            temp.swap(w);
        }
    }
    // 由于w的类型被声明为Widget,所以w必须支持Widget的接口
    // 由于Widget的某些成员函数是virtual的,所以将在运行期根据w的动态类型决定调用哪个函数
    // 即 w对那些函数的调用表现出运行期多态。
    
  • 如果将上述例子中的 doProcessing 函数转变为函数模板:【template 支持隐式接口和编译期多态,编译期多态实现是依赖 template 具现化和函数重载解析】

    template<typename T>
    void doProcessing(T& w)
    {
        if( w.size() > 10 && w != someNastyWidget )  {
            T temp(w);
            temp.normalize();
            temp.swap(w);
        }
    }
    // w必须支持哪一种接口,由template中执行于w身上的操作来决定。
    // 只要涉及w的任何函数调用,比如 operator > 与 !=,有可能造成template具现化
    // 这样的具现行为发生在编译期。
    
  • 以不同的 template 参数具现化 function templates 会导致调用不同的函数,这便是所谓的编译期多态。

    • 编译期多态与运行期多态的差异类似于哪一个重载函数应该被调用(发生在编译期)与哪一个 virtual 函数该被绑定(发生在运行期)。
  • 通常显式接口由函数的签名式(函数名称、参数类型、返回类型)构成,隐式接口就不同于此,并不基于函数签名式,而是由有效表达式组成。

条款 42:了解 typename 的双重意义

  • 当我们声明 template 类型参数,class 和 typename 的意义完全相同。
  • template 内出现的名称如果相依于某个 template 参数,称之为 dependent name(从属名称)。
    template<typename C>
    void print2nd(const C& container)
    {
        if(container.size() >= 2)  {
            C::const_iterator iter(container.begin());
            ++iter;
            int value = *iter;
            std::cout<<value;
        }
    }
    // 这里的iter就是从属名称
        // template内出现的名称如果相依于某个template参数,称之为 dependent name(从属名称)
        // 如果从属名称在 class 内呈嵌套状,我们称它为嵌套从属名称
    // 而这里的value就是local变量,并不依赖于任何template参数,也叫非从属名称
    
  • 嵌套从属名称可能会导致解析困难。
    • 如果解析器在 template 中遭遇一个嵌套从属名称,它便假设这名称不是个类型,除非你告诉它是。【缺省情况下嵌套从属名称不是类型】
    • 意思就是说:想在 template 中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置加上关键字 typename
  • 前面说了,嵌套从属类型名称的前缀词是 typename,但也有不可使用的情况:
    • typename 不可以出现在 base_class list 内的嵌套从属名称类型名称之前;
    • 也不可以在 member initialization list(成员初值列)中作为 base class 修饰符。
  • typename 一般和 typedef 共同使用。

条款 43:学会处理模板化基类内的名称

  • 假设 B 是一个模板化基类,D 继承了 B,默认情况下,D 中不能调用 B 的方法,因为 C++ 往往拒绝在 templatized base classes(即 base 类是模板)内寻找继承而来的名称。
    • 因为 C++ 知道 base class templates 有可能被特化,而特化版本可能不提供一般性 template 相同的接口。
  • 为解决上述问题,有三种方法:【告诉编译器基类中拥有派生类所需函数】
    • 第一种方法,可在 derived class templates 内通过 this 指针来调用模板化基类中的方法(相当于明确表示子类将会实现基类的方法)。
      template <typename object>
      class B:public A<object>
      {
      public:
          void func3()
          {
              this->func1();//加上this关键字
          }
      };
      
      • 第二种方法是使用 using 声明式。
        template <typename object>
        class B:public A<object>
        {
            using A<object>::func1;
        public:
            void func3()
            {
                this->func1();//加上this关键字
            }
        };
        
    • 第三种方法是通过 :: 符号明确指出调用的函数位于基类中,但这种方法最差劲,因为如果是调用的虚函数,将会关闭动态绑定行为。
      template <typename object>
      class B:public A<object>
      {
      public:
          void func3()
          {
              A<object>::func1();
          }
      };
      

条款 44:将与参数无关的代码抽离 templates

  • Template 是节省时间和避免代码重复的一个奇妙的方法。
    • class templates 的成员函数只有在被使用时才被暗中具现化。(fuction template 也差不多)
  • 使用 templates 可能会导致代码膨胀:其二进制码带着重复的代码、数据或两者都有。
  • 为了避免这类错误,需要进行共性与变性分析。
    • 对于函数:分析两个函数,找出共同的部分和变化的部分,把共同的部分搬到一个新函数去,保留变化的部分在原函数中不动。
    • 对于类:令原先的 classes 取用共同特性,而原 classes 的互异部分仍然在原位置不动。
    • 对于 template:
      • template 生成多个 class 和多个函数,所以任何 template 代码都不该与某个造成膨胀的 template 参数产生相依关系。
      • 因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数或 class 成员变量替换 template 参数。
      • 因类型参数(type parameter)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享实现码。

条款 45:运用成员函数模板接受所有兼容类型

  • 同一个 template 的不同具现体之间并不存在什么与生俱来的固有关系。
    • 即编译器将 SmartPtr<A>SmartPtr<B> 看做两个完全不同的类。
  • 使用 member function templates 生成可接受所有兼容类型的函数:【泛化 copy 构造函数】
    template<typename T>
    class SmartPtr  {
    public:
        template<typename U>
        SmartPtr(const SmartPtr<U>& other);    // 生成copy构造函数
        ...
    };
    // 上述代码是指:对任何类型T和任何类型U,这里可以根据SmartPtr<U>生成一个SmartPtr<T>
    // 因为 SmartPtr<T> 有个构造函数接受一个SmartPtr<U>参数。
    
  • 我们希望根据一个 SmartPtr<Derived> 创建一个 SmartPtr<Base>,却不希望根据一个 SmartPtr<Base> 创建一个 SmartPtr<Derived>,所以就需要对成员 template 产生的函数群进行拣选或筛选:
    template<typename T>
    class SmartPtr  {
    public:
        template<typename U>
        SmartPtr(const SmartPtr<U>& other) : heldPtr(other.get())    // 以other的heldPtr
        {  ...  }    // 初始化this的heldPtr
        T* get() const  {  return heldPtr;  }    
        ...
    private:
        T* heldPtr;    // 这个SmartPtr持有的内置指针
    };
    // 使用成员初值列(member initialization list)来初始化
    	// SmartPtr<T> 之内类型为T* 的成员变量;
    // 并以类型为U* 指针(由SmartPtr 持有)作为初值。
    
  • 如果声明 member templates 用于泛化 copy 构造或泛化 assignment 操作,还是需要声明正常的 copy 构造函数和 copy assignment 操作符。

条款 46:需要类型转换时请为模版定义非成员函数

  • 本条款是在条款 24 的基础上,讲述的有关非成员函数在模板类中(non-member function template)的作用。【条款 24 的 template 版本】
    • 条款 24 讲述了我们怎样能实现类的对象在特定条件下的隐式转换问题。【条款 24 是用 non-member】
  • template 实参推导过程中从不将隐式类型转换函数考虑在内。
  • 在一个 class template 内部,template 名称可被用来作为 template 和其参数的简略表达方式。(比如可以不写 <int>
  • 当我们编写一个 class template,而它所提供之与此 template 相关的函数支持所有参数之隐式类型转换时,请将那些函数定义为 class template 内部的 friend 函数。

条款 47:请使用 traits classes 表现类型信息

  • STL 迭代器分类:
    • input 迭代器:只能一次一步向前移动,客户只可读取(不能涂写)且只能读取一次它们所指的东西,模仿指向输入文件的阅读指针;例如 istream_iterators。
    • output 迭代器:与 input 迭代器类似,但一切只为输出,只能一次一步向前移动,客户只可涂写(不能读取)且只能涂写一次它们所指向的东西,模仿指向输出文件的涂写指针;例如 ostream_iterators。
    • forward 迭代器:具有 input 迭代器和 output 迭代器的所有功能,只能一次一步向前移动,可以读或写其所指物一次以上;STL并未提供单向 linked list,但某些程序库有(通常名为 slist),这种容器的迭代器就是 forward 迭代器。
    • bidirectional 迭代器:它除了可以向前移动,还可以向后移动,一步只能一次,并可以读或写所指物一次以上;STL 的 list、set、multiset、map 和 multimap 的迭代器就属于这一类。
    • random 迭代器:除了 bidirectional 迭代器的所有功能以外,还可以执行迭代器算数,即在常量时间内向前或向后移动任意距离;例如 vector、deque 和 string 的迭代器。
  • trait classes 的使用过程如下:
    • 建立一组重载函数(身份像劳工)或函数模板,彼此之间的差异仅在于各自的 traits 参数;令每个函数实现码与其接受之 traits 相应和。
    • 建立一个控制函数(身份像工头)或函数模板,它调用上述劳工函数并传递 traits classes 所提供的信息。

条款 48:认识 template 元编程

  • 模板元编程可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值