美文网首页
23章 模板

23章 模板

作者: my_passion | 来源:发表于2022-06-26 12:27 被阅读0次

Note

    (1) 哲学: 模板 应该从>=1个 具体实例 `泛化` 而得 
    
    (2) 支持 `类型/值` 作 `参数` 的 程序设计: 直接支持 GP
    
    (3) 优势 
    
        实例化时 获得 `模板 + 模板实参` 信息 -> `灵活性 & 运行时性能(无与伦比)` 
        
        1) `时/空效率 不逊            : 相比手工 code 

        2) `参数类型 不必显式相关`    : 相比 类层次

        3) 类型安全 
        
    (4) 代价: (编译器) `类型检查 复杂` 

    (5) 最大弱点: `无法直接表达` 对 `模板实参 的 要求`
    
        `概念 Concept: 表达 对 模板参数的要求` 
                        
        (视为) 谓词: 接受 `类型参数`, 返回 true / false 
                            
            "C 是容器 吗?"
                Container<Vector<int> >() / Container<int>() 应该返回 true / false
    
    (6) 简单操作 < [] 等 -> 生成 单个机器指令 -> `模板 + inline -> 大幅缩减代码量 + 提高运行速度` 
    
    (7) 类型 别名: 短小 / 封装
        using string = std::basic_string<char> 

23.1 简单字符串 模板

1 模板 定义

    (1) 带限定符 的 模板名 的 scope 内, `模板名后的限定符` 多余  
            |          |                        |           |
            |/         |/                       |/          |/
           <C>       String<C>                  <C>     String<C>:: 后 用 String 即可
           
    (2) 模板类 `外部定义 成员: 必须 显示声明为模板`
            
        template<typename C>
        String<C>::String()
            : sz{0}, ptr {ch}
        {
            ch[0] = 0;
        }

2 模板 实例化: 模板 + 模板实参列表 ( -> compiler ) -> 生成 类或函数的 过程

(1) 实例化时机: 真正编译 前

(2) 特例化: 实例化 的 结果 -> 特例化版本 

23.2 类型检查

1 类型等价: 模板 + 相同 模板实参 -> 相同 类型

    相同的含义
            
    (1) `别名` 不引入新类型: String<Uchar> <=> String<unsigned char>
    
    (2) `常量表达式` -> 编译时求值: Buffer<char, 20-10> <=> Buffer<char, 10>

2 错误检测: 语法、语义 等错误

    ——————————————————————————————————————————————————————————————————————————————
                用 T / 用 T 的成员 / 接受 `T 类型 的 参数`
                  |\
                  |  含义: 3 点 
                  |
    与 `模板参数 使用`
    ——————————————————————————————————————————————————————————————————————————————
        (1) `相关` 的错误    |   最早可能 发现/检测 时刻         
                            |——————————————————————————————————————————————————                                         
                            |   [1] `编译时`: 模板 `第1次使用( 即 `给定` 了 `特定的模板实参`) 时 -> 该时刻: 实例化点` 
                            |   [2] `链接时`
    ——————————————————————————————————————————————————————————————————————————————
        (2) `无关` 的错误    |   [1] 编译时: 模板 `定义时(实例化 前)`
    ——————————————————————————————————————————————————————————————————————————————

23.3 类模板 成员

1 成员模板: 灵活性 & 控制度

    template<typename T>
    class complex
    {
        T re, im;
    public:
        template<typename U>
        complex(U r, U i)
            : re{r}, im{i} {}
    };
    
    成员模板函数 与 类模板 的 实例化版本 不是一一对应`, 还接受 extra 模板实参  

(1) 成员模板 不能是 virtual

    否则, `传统 虚调用机制 的 vtbl 无法使用` -> 需变成 `动态增加(相应项)的 vtbl` 
    每当 user 用 `新实参类型` 调 `虚成员模板` 时,      
    `Linker` 必须向其所属 `类 / 类模板 的 特例化版本` 的 `vtbl 中 添加 1 个对应项` 
                
        1] Linker 复杂
        2] 动态链接, 要与 传统技术 不同         

(2) 成员类型: 若 只 use 部分( 而非全部 ) 模板参数, 则 "被迫" 依赖 全部模板参数 -> 解决: 让 所属类模板作 友元

    链表 List<T, Alloc> 的 链接类型 Link<T>
        List<double, My_allocator>::Link 与
        List<double, Your_allocator>::Link 不等价(含义上 应该等价 )   
                
    template <...T, ... Alloc>  
    class List;
    
    template <...T>     
    class Link
    {
        template <...U, ... A>                                                           
        friend class List;                                                 
                                                                                            
        T val;          
        // ...                      
    };
    
    template <...T>
    struct Iterator 
    {
        Link<T>* currrent_pos;
    };
    
    template <...T, ... Alloc>  
    class List
    {
    public:
        Iterator<T> begin();
        // ...
    };

2 友元

(1) 友元模板函数 名后 的 空 <>: 模板类内 声明时, 必须带, 以指出 友元函数 是 模板; 否则, compile error

    模板类外 定义, 可省略
    
    template <... T> class Matrix;
    
    template <... T> 
    class Vector
    {
        T v[3];
    public:
        friend Vector operator*<>(const Matrix<T>&, const Vector&);             
    }
    
    template <... T> 
    class Matrix
    {
        T v[3];
    public:
        friend Vector<T> operator*<>(const Matrix&, const Vector<T>&);              
    }
    
    template <... T>    
    Vector<T> operator*(const Matrix<T>&, const Vector&)
    { /* ... */ } // ADL

(2) 友元(模板)类 可以是:

1) 已声明的 (非模板)类 / 模板类<模板参数> / 模板参数 / 类型别名 -> friend 之后 不能用 class(否则 compile error)

2) 引入的新类 -> ... 必须用 class

    class C;        // 前向声明 
    using C2 = C;   // 类型别名 
    
    template <typename T>
    class A
    {
        friend C;        // [2]
        friend C2;       // [4]
        freind class C3; // [5]
    };
    
    template <typename T>
    class B
    {
        friend T;       // [1]      
        friend class T; // ERROR 
        friend A<T>;    // [3]
    };
3   成员数据

    `non-static` 成员数据 `不能是 constexpr` 

4   成员函数
    
    `成员模板函数 不能是 virtual(23.3.6) / 成员非模板函数 可以是`

    本身就是模板 / 因 类模板实例化 而成为 模板

5   成员类型 别名

    (1) using / typedef 
        
    (2) 非侵入式添加类型
                            
        类型萃取 & 等价特性( auto / decltype() )`   // 28.1.4
        
    (3) 关联类型: 便于 类外访问
        
        容器 `迭代器 和 元素类型` 指定为 `别名`        
                                                    
        template<typename T>
        class Vector
        {
        public:
            using value_type = T;
            using iterator = Vector_iter<T>;
            // ...
        };

23.4 函数模板

1 函数模板 vs. 类模板

1) 区别: 实参的确定
    
    [1] 函数模板 实参推断
        
    [2] 实例化 + 特例化   
        多 Ctor -> 类模板实参推断 不可行

2) 联系: 用 推断出的类型 创建对象

[1] 模板函数: 模板实参推断 -> internal: 用 函数形参 构造 模板类对象

    template <...T1, ... T2>
    pair<T1, T2> make_pair(T1 a, T2 b) 
    { return {a, b}; }

[2] 用 类模板成员函数模板 的 arg 递归推断 类模板实参类型(T的真正类型)

返回指向 (Tuple) 第N个元素的引用 --- 递归/转换为 --- 返回 指向 basePart(elem 序号在后) 第 N-1 个元素的引用

    template <typename Ret, typaname N>
    struct getNth
    {
        template<typaname T>
        static Ret& get(T t)
        {
            return getNth<Ret, N-1>::get(*t.base() );
        }
    };
    
    template <typename Ret>
    struct getNth<Ret, 0>
    {
        template<typaname T>
        static Ret& get(T t)
        {
            return t.x;
        }
    };
    Tuple<double, intm char> t{1.0, 2, 'a'};
    
    std::cout << get<0>(t);

2 函数模板实参推断 若发挥不了作用, 就必须 显式指定模板实参

    template<...T>
    T* create(); 
    
    int* p = create<int>(); 

2 函数模板 实参推断

X 类型右值 被推断为 X
X 类型左值 被推断为 X&

(1) 引用推断: 类 保存 {整数, 指针} pair

    template <typename T>
    class Xref
    {
    private:
        int index;
        T* elem;
        bool owner;
    public:
        Xref(int i, T* p) // 保存 指针: Xref 是 owner
            : index{i}, elem{p}, owner{true} {}
            
        Xref(int i, T& r) // 保存 指向 r 的 指针, Xref 不是 owner
            : index{i}, elem{&r}, owner{false} {}
            
        Xref(int i, T&& r) // 将 r 移入 Xref, Xref 变为 owner
            : index{i}, elem{ new T{ move(r) } }, owner{true} {}
            
        ~Xref()
        {
            if(owner) delete elem;
        }
    };
    
    string x {"hello"};

    1) Xref<string> r1 {7, "hello"};
    2) Xref<string> r2 {7, x};
    3) Xref<string> r3 {7, new string{"hello"} }; // 调 Xref<string>(int i, string* p), r3 拥有 字符串 "hello"
        
    1)  Xref<string> r1 {7, "hello"};   
        
        匹配 
            template <typename T> 
            Xref<T>::Xref(int i, T&& r); 
                                    
        `X 类型` 的 `右值` 被         推断为 X
            |               |               |    
            |               |               |/
        string          "hello"      T = string
                                                    
            r: string&& 
            T: sting                        
                                    
            实例化版本                 
            Xref<string>::Xref(int i, string&& r);
            
    2) Xref<string> r2 {7, x};
     
        匹配
            template <typename T> 
            Xref<T>::Xref(int i, T& r); 
     
        `X 类型` 的 `左值` 被 推断为 X&
         |              |             |   
         |              |             | 
       string           x           string&
                
            r: string& 
            T: sting 
                
            实例化版本                      
                Xref<string>::Xref(int i, string& r);

(2) 实参转发 std::forward & 完美转发

                                左值引用 / 右值引用 (int& / int&&) ...结果是 所引类型 (int)
                                non-reference -> ...type 结果是 本身
                                    |
    template <typename T>           |
    T&& std::forward(typename remove_reference<T>::type& arg);
    
    template <typename T>
    T&& std::forward(typename remove_reference<T>::type&& arg);
    
    返回值: 
        static_cast<decltype(arg)&&>(arg)
        推导 arg 值的 左右值类型, 加上 && 
        => 转发时保持了 arg 的 左/右值属性: 是 左/右 值, 就按 左/右 值 调用 aother func
        
    template<class T>
    void wrapper(T&& arg) 
    {
        // arg is always lvalue
        foo(std::forward<T>(arg)); // Forward as lvalue or as rvalue, depending on T
    }
    
    arg 相应的实参是  arg 值类型  arg 的类型        实例化版本               T类型     调 std::forward      返回类型 
                                                                                    
        X类型的 右值 X           X&& 右值引用    void wrapper(X&& arg)   X       (::type&& arg)          X&&
                                                                                    
        X类型的 左值 X&          X&  右值引用    void wrapper(X& arg)    X&      (::type& arg)           X&

(3) 希望 从 arg 构造出 T, 且 不产生 额外 copy: make_unique()

    template<typename T, typename Arg>
    unique_ptr<T> make_unique(int i, Arg&& arg)
    {                                                           
        return unique_ptr<T> { new T{i, forward<Arg>(arg) } };
    }   

    1) auto p1 = 
        make_unique<Xref<string> >(7, "hello");
            | template...
            | make_unique(int i, Arg&& arg)
            |
    string 类型 的 右值 "hello" 被 推断为 string
            |               
            |   `右值引用传递` / Arg = string
            |                       
    make_unique(int i, string&& arg)                
            
            unique_ptr<T> { new T{i, forward<Arg>(arg) } }  
                        |        |                          
                        |        |      
                Xref<string>     |  调           
                                 |/
                                Xref<string>::Xref(int, string&&)
                                                
                                      
    2) auto p1 = 
        make_unique<Xref<string> >(7, x);
          | template...
          | make_unique(int i, Arg&& arg)
          |
    string 类型 的 左值 x 被 推断为 string&
          |             
          | `左值引用传递` / Arg = string&
          |                     
    make_unique(int i, string& arg)                 
        
        unique_ptr<T> { new T{i, forward<Arg>(arg) } }  
                    |        |              
                    |        |      
            Xref<string>     |  调           
                             |/
                            Xref<string>::Xref(int, string&)
    
3 函数模板 重载(12.3.1)

23.5 模板 别名

    1 只能用 using 不能用 typedef 

    2 `不能定义` 别名的 `特例化版本`

    3 模板别名 使用时 
    
    ——————————————————————————————————————————————————————————————————————
    用到了 特例化 : 得 正确 特例化版本 
    ——————————————————————————————————————————————————————————————————————
    没用到 特例化 : 不能 将 `别名` 简单视为 原始模板中相应名 
                    template <int N>
                    using int_exact = typename int_exact_traits<N>::type; 
                        
                    int_exact 与 int_exact_traits<N>::type 行为不同              
    ——————————————————————————————————————————————————————————————————————

23.6 源码组织: 不支持 分离编译

1. C++ 不支持 模板 定义和使用 分离编译: 使用前 (只) 包含其声明 & other 编译单元 定义

解决: 模板 使用前 (只) 包含其声明 & "稍后包含 模板定义" ( 在本编译单元 使用之后 )

    1) 特殊: 稍后 #include "模板定义源文件.cpp"
    
    2) 将 `模板实现 对 user 代码` 造成 `不良影响` 的机会 `降到最低`  
        
    3) 代价: `user code 中 宏` (等) 对 模板定义 可能造成影响                                        

2 链接

(1) 动态链接: 模板使用处 单一定义原则/ODR

(2) 封装 复杂模板库 于 非模板接口函数

    double accum(const vector<double>& v)
    {
        return accumulate(v.begin(), v.end(), 0.0);
    }
    
    对外, 只需 该非模板接口函数声明 
    double accum(const vector<double>& v);
函数模板实参推断: special 处.jpg 函数模板重载 与 派生 组合使用.jpg 用 函数模板 推断 类模板 实参类型: 第2个例子.jpg 接上图.jpg 模板源码组织 之 "稍后包含模板定义".jpg 建议.jpg

相关文章

  • 领导力培训和发展行业的电子学习模板

    模板一: 模板二: 模板三: 模板四: 模板五: 模板六: 模板七: 模板八: 模板九: 模板十: 模板十一: 模...

  • 有了这套画册模板设计之路不再迷茫!超赞!

    indesign模板介绍 时尚写真画册模板、婚礼画册模板、摄影写真画册模板、 人物展示画册模板、服装展示画册模板;...

  • 08_模板层补充

    模板层 一、模板层导入与继承 模板导入: 先码一个好看的模板 语法:{% include '模板名称' %} 模板...

  • 《PHP Learning》模板引擎

    《PHP Learning》模板引擎 模板处理使用正则处理模板替换规则保存模板编译结果 模板使用 自定义模板引擎 ...

  • C++模板编程/泛型编程

    模板分类 模板分为函数模板与类模板两类。 函数模板 模板声明 模板定义 例如 函数模板实参类型不一致问题 参数推导...

  • 14/15

    约束模板友元 模板类的外边定义一个模板函数,在模板类中将模板函数具体化为模板类的友元函数 非约束模板友元 模板类中...

  • ★07.关于类模板

    简述 类模板:是类类型的模板,如:vector。 模板类:类模板的实例化,如:vector 。 类模板的模板参数无...

  • 【C++】C++学习笔记之十六:模板特化

    模板特化分为两种: 模板全特化 模板偏特化 模板全特化 模板偏特化

  • thymeleaf(五) ____文本模板模式

    thymeleaf中的三个文本模板 text模板,javascript模板,css模板是文本模板.而thymel...

  • 14/12

    成员模板:模板类中成员变量是模板类对象(1),模板类中函数是模板函数(2)

网友评论

      本文标题:23章 模板

      本文链接:https://www.haomeiwen.com/subject/zzzwvrtx.html