• 【读书笔记】【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对那些函数的调用表现出运行期多态。
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • 如果将上述例子中的 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具现化
      // 这样的具现行为发生在编译期。
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 以不同的 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参数,也叫非从属名称
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 嵌套从属名称可能会导致解析困难。
      • 如果解析器在 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关键字
            }
        };
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 第二种方法是使用 using 声明式。
          template <typename object>
          class B:public A<object>
          {
              using A<object>::func1;
          public:
              void func3()
              {
                  this->func1();//加上this关键字
              }
          };
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
      • 第三种方法是通过 :: 符号明确指出调用的函数位于基类中,但这种方法最差劲,因为如果是调用的虚函数,将会关闭动态绑定行为。
        template <typename object>
        class B:public A<object>
        {
        public:
            void func3()
            {
                A<object>::func1();
            }
        };
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9

    条款 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:运用成员函数模板接受所有兼容类型

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

    • 本条款是在条款 24 的基础上,讲述的有关非成员函数在模板类中(non-member function template)的作用。【条款 24 的 template 版本】
      • 条款 24 讲述了我们怎样能实现类的对象在特定条件下的隐式转换问题。【条款 24 是用 non-member】
    • template 实参推导过程中从不将隐式类型转换函数考虑在内。
    • 在一个 class template 内部,template 名称可被用来作为 template 和其参数的简略表达方式。(比如可以不写
    • 当我们编写一个 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 元编程

    • 模板元编程可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
  • 相关阅读:
    C++多态与虚拟:C++编译器对函数名的改编(Name Mangling)
    论文笔记:Spatial-Temporal Large Language Model for Traffic Prediction
    单位互联网网络时断时续的问题
    ChatGPT人工智能:ai大模型应用开发源码搭建
    编写脚本一键安装rsyslog
    Java NIO Selector选择器源码分析
    Linux之Shell进阶(变量和条件判定语句)
    Boost库学习笔记(二)算法模块-C++11标准
    ctfshow-nodejs
    《Head First HTML5 javascript》第2章 数据存储 知识点总结
  • 原文地址:https://blog.csdn.net/weixin_44705592/article/details/126943053