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