美文网首页alreadyc/c++
24-26章: GP / 特例化 / 实例化

24-26章: GP / 特例化 / 实例化

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

24章 GP/泛型程序设计

Note: 模板

(1) 为 `编译时计算` 和 `类型处理` 提供 `强有力的机制` 
                                                            
    [1] 传递 `类型参数` 而 不丢失信息    : 有大好机会 `inline`
    [2] 传递 `非类型/常量 参数`       : 能进行 `编译期计算` 
    [3] `推迟 类型检查` 到 实例化/link 时: 有机会将 `不同 context 信息 编织在一起`  
            
(2) 用途
        
1) 支持 GP: `通用算法` 设计

2) TMP / template metaprogramming ) / 模板元编程 
        
    [1] 关注 `代码生成` 技术
    
        `类型 作 模板实参:` 并不要求使用 `类层次 / RTTI -> 无额外开销 & 逻辑上合理`
        
        `类型安全: 杜绝 类型转换`
        
        `类型检查: 在 模板定义 时, 检查 实参的使用`, 而不是 在模板声明时 检查 显式接口
        
    [2] 依赖 `类型函数(编译时多态)` 表示 `编译期计算` -> 高性能 

3) 对 值 进行 操作: C++ 处理 模板实参 的方式

    编译时 没有对象
    像 `动态类型语言(如 python) 编程`, 但          
        [1] 没有 `运行时开销`      
        [2] 动态类型语言中 `运行时异常错误` 变成 C++ 中 `编译时错误`  

24.1 算法 和 提升

1 提升: 具体 到 抽象 泛化过程 & 保持 性能 + 合理

    特定数据类型  上       特定操作     的 函数 
        |                       |
        |                       |   
        |/                      |/
    多种数据类型  上       更通用的操作 的 算法 

25章 特例化

Note

    (1) 特例化+特例化 目标: 灵活性 & 性能

25.1 模板参数 和 模板实参

            作 实参 传给
    模板实参 —— —— —— —— —— —— ——> 模板参数 

    —————————————————————————————————————————————————————————————————————————————————————————————————————-
        3 种 `模板参数`           |  是否用 typename/class  |           例 
    —————————————————————————————————————————————————————————————————————————————————————————————————————-
    类型类型 的  `类型` 参数  |  用                       |         函数对象  
    ————————————————————————————————————————————————————————————————————————————   `操作` 参数 
    内置类型 的  `值`   参数  | 不用                  |   int / 函数指针 
    ——————————————————————————————————————————————————————————————————————————————————————————————————————
    模板类型 的  `模板`  参数 |  不用, 但用 template     |   第2模板参数 (是 模板) 依赖于 `第1模板参数`
    ——————————————————————————————————————————————————————————————————————————————————————————————————————
        
    —————————————————————————————————————————   
    排列组合出 4 种常见 `模板实参` 
    —————————————————————————————————————————
    类型 模板实参 |   类型 (类型) 作 模板实参
    —————————————————————————————————————————
    值    模板实参 | 值    (类型) 作 模板实参
    —————————————————————————————————————————
    操作 模板实参 |   操作 (类型) 作 模板实参
    —————————————————————————————————————————
    模板 模板实参 |   模板 (类型) 作 模板实参
    —————————————————————————————————————————

1 类型 模板实参: 无约束 & 是否有效, 完全 依赖于 模板如何使用它

    一般性约束 
        |
        |   可实现为 
        |/
    Concept 
    
    vector<double> vector< complex<double> >

2 值 模板实参

(1) 可以是 

    [1] `整型常量表达式` 
    [2] `外部链接` 属性的 `对象/函数 指针或引用`
    [3] 成员指针 且 成员非重载 
    [4] 空指针/nullptr

(2) 应用
                    
    [1] 整型   模板实参: 提供 `大小 和 限制`
    [2] 函数指针 模板实参: 操作 作 模板实参 的 一种 
    [3] 模板参数列表 中, `类型 模板参数` 出现后即可当作 `值 模板参数 的类型` 
            
    template<typename T, int max>
    class Buffer
    {
        T v[max];
    public:
        Buffer() {}
    };
    
    Buffer<char, 128> cbuf;

    template<typename T, T default_val = T{} >
    class Vec;          

3 操作 模板实参

(1) 2 大类 
    
    1) 函数指针 模板参数 
            不灵活: 函数指针 是 值 模板实参, 不能作 类型类型 
                
    2) 函数对象 模板参数 
        
        优点 
            (1) 灵活 
            (2) `更适合于 inline` 
        
(2) 为 `操作命名` 从 `设计和维护` 角度 很有用
    
    (匿名)lambda 不能转换 为 函数对象类型 
            |
            |   想转换
            |/
        命名 lambda ( auto ) + decltype() 

(3) 3 种 操作 类型: 函数指针 / 函数对象 / lambda 比较 
    
    1) 函数指针 是 值 类型 => `不能用 typename 限定` ) 
        
        template <typename Key, typename V, bool (*cmp)(const Key&, const Key&) >
        class map1;
    
    2) 函数对象 -> 是否能 (隐式)转换 为 -> 函数指针
        
        `带 转换为函数指针` 的 `类型转换运算符` 时, 能 

    3) lambda -> 是否能 转换 为 -> 函数指针
        
        匿名(普通) lamnda: 能

    4) lambda -> 不能 转换 为 -> 函数对象类型 
        
        => 想 用 lambda 相应的 函数对象类型 
            
            命名 lambda ( auto ) + decltype()

    // ========== test.cpp
    #include <iostream>
    #include <string>

    // =======1 函数指针 ( 值 类型 => 不能用 typename 限定 ) 作 模板参数 + 函数指针 作 模板实参

    // 版本1: 函数指针 作 模板参数 + 函数指针 作 模板实参
    template <typename Key, typename V, bool (*cmp)(const Key&, const Key&) >
    class map1
    {
    public:
        map1() { /* ... */ }
        // ...
    };

    bool insensitive(const std::string& s1, const std::string& s2) { /* ... */ return true;  }

    void test1()
    {
        map1<std::string, int, insensitive> m;
    }

    // 版本2: 函数指针 别名 作 模板参数 + 函数指针 作 模板实参 <=> 版本1
    template<typename T>
        using CmpPtr = bool (*)(const T&, const T&);

    template <typename Key, typename V, CmpPtr<Key> > 
    class map2
    {
    public:
        map2() { /* ... */ }

        map2(CmpPtr<Key> c) : cmpPtr{ c } { /* ... */ }
        // ...
    private:
        CmpPtr<Key> cmpPtr;
    };

    void test2()
    {
        map2<std::string, int, insensitive> m2;

        // Note: 编译报错: 第3模板参数 只能是 值(类型), 不能是 类型(类型)
        //map2<std::string, int, CmpPtr<std::string> > m21(insensitive); 
    }

    // =======2 函数对象 ( 类型 类型 ) 作 模板参数
    template <typename Key, typename V, typename Compare = std::less<Key> >
    class map3
    {
    public:
        map3() { /* ... */ }

        map3(Compare c) : cmp{ c } { /* ... */ } // 覆盖 默认 Ctor
        // ...
    private:
        Compare cmp {};                          // 默认比较 
    };

    // 函数对象
    class Cmp
    {
    public:
        bool operator()(const std::string& a, const std::string& b) { return a < b; }
    };

    // 函数对象: 带 转换为函数指针的 `类型转换运算符`
    class Cmp2
    {
    public:
        bool operator()(const std::string& a, const std::string& b) { return a < b; }

        operator CmpPtr<std::string>() { return insensitive; }
    };

    void test3()
    {
        map3<std::string, int, std::greater<std::string> > m3;

        // 版本3: 函数对象 作 模板参数 + 函数指针 作 模板实参 
        map3<std::string, int, CmpPtr<std::string> > m31{ insensitive };

        // 版本4: 函数对象 作 模板参数 + 能转换为 函数指针 的 lambda (匿名 lambda)  作 模板实参 
        map3<std::string, int, CmpPtr<std::string> > m32{ [](const std::string& a, const std::string& b) { return a < b; } };

        // 版本5: 函数对象 作 模板参数 + 能转换为 函数指针 的 函数对象 (必须提供 转换为函数指针的 类型转换运算符) 作 模板实参 
        Cmp2 cmp2;
        map3<std::string, int, CmpPtr<std::string> > m33{ cmp2 };

        // Cmp cmp;
        // Note: 编译报错: 无法从 函数对象 cmp 转换为 CmpPtr<std::string> 类型
        // map3<std::string, int, CmpPtr<std::string> > m34( cmp ); 

    }

    void test4()
    {
        // Note: 编译报错: lambda -> 不能 转换 为 -> 函数对象类型
        // 想 用 lambda 相应的 函数对象类型 -> solu: 命名 lambda ( auto ) + decltype()
        // map3<std::string, int, Cmp > m35{ [](const std::string& a, const std::string& b) { return a < b; } };

        auto cmp = [](const std::string& a, const std::string& b) { return a < b; };
        map3<std::string, int, decltype(cmp) > m36{ cmp };
    }

    int main()
    {
        test1();
        test2();
        test3();

        return 0;
    }

4 模板 模板实参: 想用 多种实参类型(T/T*)模板 模板参数(C) 实例化

第2模板参数 为 模板类型, 且 其模板参数 是 第1模板参数 => 其模板参数 可省

    模板只需要 `一两个 容器`: 没必要传递 模板模板参数, 传递 `容器类型` 更好 
            
    #include <vector>

    template<typename T, template <typename> class C > 
    class Xrefd                        
    {                 
        C<T> mems;
        C<T*> refs;
    };

    template<typename T>
    using My_vec = std::vector<T>;

    void test()
    {
        Xrefd<int, My_vec> x;
    }

    int main()
    {
        test();
    }

    // ===      
    template<typename C, typename C2 > 
    class Xrefd2
    {
        C mems;
        C2 refs;
    };

    template<typename T, typename C = deque<T> >
    class deque;
    {
    protected:
        C c; // 底层容器 c
    };

5 默认 模板实参

    (1) std::less<Key> 通常是 最好的选择
        
        template<typename Key, typename V, typename Compare = std::less<Key> >
        class map
        {
        public:
            explicit map(const Compare& comp = {});
            // ...                          |
        };                                  |/
                默认比较对象 初始化: 空列表 {} 初始化
    
    (2) 函数模板 默认模板实参 
        
        函数模板: `所有模板参数 都有 默认实参` 时, `尖括号 <> 可 省略`
        
        template<typename Target = string, typename Source = string>
        Target to(Source arg);
        
        auto x = to<>(1.2);
        auto x = to(1.2);
        
        auto x = to<string>(1.2);         // Source 被推断为 double, 而不是用 默认实参 string
        auto x = to<string, double>(1.2); // 太繁琐 

25.2 特例化

1 为 共同接口(即 主模板) 提供 可选实现 --- 视为 ---> 接口 特例化: 为了 修改接口 (乃至 表示)

通过 最大化 共享代码量 来 最小化 代码膨胀

(1) 特例化 Vector<T*> 是 private 实现类 Vector<void*> 的 接口
    
    template<typename T>
    class Vector {/**/};
    
            不必指明 模板参数
             |\ 
             | template<> 表示 
             |      
    template<>              
    class Vector<void*> // 全特化: 使用时 `不再 指定或推断` 任何模板参数
    {
        void** p;
        // ...
        void*& operator[] (int i);
    };       |
             |/
            指针 的 引用

    template<typename T>
    class Vector<T*>: private Vector<void*> // 部分特例化
    {
    public:
        using Base = Vector<void*>;
        
        Vector() {}
        explicit Vector(int i): Base(i) {}
        
        T*& operator[](int i) { return reinterpret_cast<T*&>(Base::operator[](i) ); }
        // ...
    };

2 主模板

(1) 为 所有 特例化版本 定义 接口

(2) 被 选择(匹配) 后, 才会考虑 特例化版本

3 函数模板 特例化: 只支持 全特例化 & 很少考虑

    (1) less() 针对 const char* 的 特例化 
        
        // 版本 1
        template<>
        bool less<const char*>(const char* a, const char* b)
        {
            return strcmp(a, b);
        }
            |
            |   模板实参 可 推断出 => 去 模板实参 
            |/
        // 版本 2 
        template<>
        bool less<>(const char* a, const char* b)
        {
            return strcmp(a, b);
        }
            |
            |   给定 前缀 template<> => 模板名 less 后 尖括号 <> 多余 
            |/
        // 版本 3
        template<>
        bool less(const char* a, const char* b)
        {
            return strcmp(a, b);
        }
            |
            |   特例化 与 重载 微乎其微 => 去 前缀 template<>
            |/
        bool less(const char* a, const char* b)
        {
            return strcmp(a, b);
        }
        
    (2) `无实参函数` 
            
        template<typename T >
        T max();
        
        template<>
        constexpr int max<int>() { return INT_MAX; }
        
        template<>
        constexpr char max<char>() { return CHAR_MAX; }
            |
            |   等价 函数重载 版本: 哑实参 
            |/
        int max(int) { return INT_MAX; }
        char max(char) { return CHAR_MAX; }
        
            |   用 `类型函数 Value_type` 获得 `Iter 所指对象 的 类型`  
            |/
        template <typename Iter>
        Iter f(Iter p)
        {
            auto x = max(Value_type<Iter>{} );
        }       
建议.jpg

26章 实例化

核心: 名字绑定 + 其 所暗示的 程序设计风格

26.1 模板 实例化

1 实例化 vs. 特例化

    实例化
        模板定义 + 模板使用 -> compiler `生成 代码` 过程的: 模板 `实例化`
        
        `生成的代码` 称为 特例化版本
            类定义 + `用到的 成员函数` 定义
            函数 

    ————————————————————————————————————————————————————————————        
    实例化
    ————————————————————————————————————————————————————————————                                    
                                    实例化
        [1] 主模板                     - - - ->    生成特例化 version
        
                                    实例化
        [2] 用户特例化(部分特化)    - - - -> 用户特例化 version
    ————————————————————————————————————————————————————————————
    
    用户特例化
        [1] 偏特化/部分特化
        [2] 全特化: 不再进行 实例化, 本身就是 用户特例化 version

2 何时 需 实例化: 模板在 使用时

[1] 模板类 : 需 类定义 时, not 声明类指针
[2] 模板函数: 调用 / 取地址 时

模板 使用处 定义了1个 实例化点

26.2 名字绑定 & 生成代码

名字绑定: 为 模板 显式或隐式 使用的 每个 name 寻找其 声明 的 过程

    模板实例化 涉及 `模板定义 / 模板使用 / 实参类型声明` 3 个 context 
                
———————————————————————————————————————————————————————————————————————————————————————————————————————         
C++ 将 `模板定义` 中  |   名字绑定 何时完成 | 若 为 类型名, 是否必须           |   是否可以 `稍后声明`
    使用的名字 分为        |                     |  用 typaname 明确告诉 compiler   |
———————————————————————————————————————————————————————————————————————————————————————————————————————         
[1] 依赖性名字           |   `实例化点 绑定`     |     是                           |   是                       
———————————————————————————————————————————————————————————————————————————————————————————————————————
[2] 非依赖性名字          |   `模板定义点 绑定` |        否                           |   否                       
———————————————————————————————————————————————————————————————————————————————————————————————————————
    
    依赖性: 依赖于 模板参数 T 

1 依赖性名字

(1) 可以是 函数名: 只要 func 实参 或 形参 明显依赖于 模板参数 T

    [1] `函数实参` 类型依赖于 `模板参数 T`
        f(T(1) ) / f(t) / f( g(t) ) / f(&t)  : t 类型是 T

    [2] `函数形参` 依赖于 `模板参数 T` 
        f(T) / f(list<T>&) / f(const T*)

(2) 可稍后声明

    // eg1
    template <typename T>
    T f(T t)
    {
        g(t); // t 是 依赖性名字 => g 是 依赖性名字 => g 可以 稍后声明 
    }   |
        | 稍后声明
_ _ _ _ |
|   
|   class Quad { /* ... */ };
|   int g(Quad);
|       |\
|_ _ _ _|
        
    // eg2
    class Quad { /* ... */ };
    
    template <typename T>
    T ff(T t)
    {
        gg(Quad{1} ); // error: scope 内 没有 gg() 声明. g 不是依赖性名字 => 不能稍后声明 
    }
    
    int gg(Quad);

(3) 被 compiler 默认视为 非类型名

    解决:
    [1] `typename` 显式告诉 compiler (其后的 name 是 类型)
    
        与用 template 告诉 compiler 其后的 成员模板 是 template 类似
        
    [2] `类型别名: 封装 typename`
    [3] auto 让 compiler 推断 

    template<typename T>
    using Value_type<T> = typename T::value_type;
                         
    tmplate<typename C>                  
    void f(C& c)
    {
        typename C::value_type v = c[0]; // [1] typename 显式说明
        auto v2 = c[0];                  // [3] auto 让 compiler 推断 
        Value_type<C> v2 = c[0];         // [2] 类型别名: 封装 typename 
    }

(4) .(点) -> :: 后的 成员模板 需使用 关键字 template: 告诉 compiler 其后的 成员模板 是 template

    class Pool
    {
    public:
        template<typename T>
        T* get();
        // ...
    };
    
    template<typename Alloc>
    void f(Alloc& alloc)
    {
        int* p1 = alloc.get<int>(); // error: compiler 默认假定 get() `不是 模板名` 
        
        int* p1 = alloc.template get<int>(); 
    }                           

2 实例化点 绑定

模板 使用点 定义了1个 实例化点

实例化点位置使用点 之后/前最近 global 或 namespace scope: 函数模板 / 类模板 & 类成员

    ——————————————————————————————————————————————————————————————————————————————————————————————  
                        |   实例化点位置 
    ——————————————————————————————————————————————————————————————————————————————————————————————
    [1] 对 `函数模板`   |    在 `使用点 之后` 的 最近 global/namespace scope
                        |       1] 保证 `函数调用 的 正确性`  
                        |       2] `允许 递归调用` 
    ——————————————————————————————————————————————————————————————————————————————————————————————                  
    [2] 对 `类模板`     |   在 `使用点 之前`...
        或 `类成员`     |       保证 `使用点 之后`, 可以用 `实例化出的对象` 调其 `成员函数`    
    ——————————————————————————————————————————————————————————————————————————————————————————————

[1]             
    void g(int);
    
    template<typename T>
    void f(T t)
    {
        g(t);
        
        if(t)
            h(t - 1);
    }
    
    void h(int i)
    {
        extern void g(double);
        f(i); // `使用点` 定义了 `实例化点`, 但 `实例化点位置` 在 `使用点 之后` 的 最近 global/namespace scope
                        |           
    }                   |/
    // f<int> 的 `实例化点` 
    
    // 若 `实例化点 位置` 为 `使用点 位置`   
    1] h() 中 调 f() 中 的 g() 是 `h() 中 局部的 g(double)`, 而非 期望的 `全局 g(int)`
    2] h(i) 的 间接递归调用 无法处理 
                                            
[2] 
    template<typename T>
    class Container
    {
        vector<T> v;
    public:
        void sort();
    };
    
    // Container<int> 的 `实例化点`
    void f()                 |\
    {                        |
        Container<int> c; // `使用点` 
        c.sort();
    }
    // Container<int> 的 `实例化点`  若在 f() 之后, c.sort() 就 找不到 Container<int> 的定义了 

3 多实例化点: 相同 模板实参 + 多次使用 同一模板 -> 多实例化点: 不同的绑定 -> 二义性

解决: 限制 模板中 context 依赖

代码膨胀: 模板 1个头文件中定义, 多源文件中使用 & 相同 模板实参 
    -> 多份 相同的实例化版本 -> 代码膨胀 -> 如何解决 ?
    -> 接口特例化: 25.2
(1) 在 `重载函数 两个声明之间 调用它` -> 坏风格  
    解决: 在所有重载函数声明后调用 -> `最佳匹配 / 无 二义性`  
    |
    |   隐身
    |/
(2) `分离的编译单元` : 类似错误检测困难 -> 应尽量 `限制 模板中 context 依赖`

4 模板 和 namespace: ADL

ADL 引入的 find scope 比 当前 scope 的 模板函数 更 匹配, 
    而 coder 本想用后者 却因为忘记用 当前 namespace 限定 -> 匹配到 前者

解决: 用 当前 namespace 限定

5 来自基类 的 名字

    如何引入 ?
    ——————————————————————————————————————————————————————————
    [1] 依赖性类型 限定 name   |
    ——————————————————————————————————————————————————————————          
    [2] using 声明 引入         |   using T::g(不用写 形参);
    ——————————————————————————————————————————————————————————
    [3] this 指针限定           |   this->g(实参);
    ——————————————————————————————————————————————————————————              
多实例化点: 二义性 + 非 二义性.jpg 来自基类 的 名字: 明确依赖关系.jpg 来自基类 的 名字: 明确依赖关系 (接上图).jpg 建议.jpg

相关文章

网友评论

    本文标题:24-26章: GP / 特例化 / 实例化

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