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
网友评论