C++ 模板元编程笔记

PREFACE


参考书籍Reference Book:

  • C++ Templates: The Complete Guide 2nd Edition


版权声明:本文受到 Creative Commons BY-NC-SA 协议的著作权保护,转载或改编时,请署名原作者。

Copyright: WRITTEN BY RYKER ZHU in Shanghai under CC BY-NC-SA CC BY-NC-SA

提示:您可以通过单击网页右侧的目录以快速导航至您需要查看的内容。

Tips: You can click on the table of contents on the right sidebar of the web page to navigate.

本笔记为中英双语,在没有合适的中文对应翻译时,会直接保留英文原名,不便之处敬请谅解。

除有特殊说明的外,所有以粗斜体标出的皆为概念、以粗体标出的为关键点或者小节标题。文中所有代码皆使用 Apache License 2.0 协议授权。

2023年 夏
于上海
祝禾 著


The Basics

Function Templates

编译模板需要经过两个阶段:

  1. 定义阶段 (Definition Time),在该阶段会在忽略掉模板参数的前提下检查错误,也就是和模板参数无关的错误;
  2. 实例化阶段 (Instantiation Time),在该阶段经过模板实例化之后,会进行全面错误检查。

Class Templates

Nontype Template Parameters

Variadic Templates

Tricky Basics

Move Semantics and enable_if<>

C++11 起引入了移动语义 (Move Semantics) 这一特性,利用这个特性可以避免在调用拷贝构造函数时,其深度拷贝引发的时间和空间开销问题。

一类应用移动语义特性的典型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Object {
public:
// Move Semantics
Object(Object&& another) {
this->data = another.data;
another.data = nullptr;
}

~Object() { delete data; }

private:
void* data;
};

在上述代码中 another 这个形参是右值引用 Object && 的形式,“移动” 所做的就是将传入的右值的字段 data 移为己有;为了保证调用析构函数 ~Object() 时,data 指向的对象不会被析构两次(右值引用的值在表达式语句结束之后就会马上被析构),所以将数据偷过来之后还需要把原本右值的字段变成空指针,这样析构空指针就不会引发错误了。

简而言之,移动语义就只干了两件事:

  1. 将右值的字段移为己用;
  2. 置空右值字段。

由于本质上只是传递了指针,其时间和空间开销近乎可以忽略不计,相比深度拷贝来说,所需的代价小了不少。

Perfect Forwarding

完美转发 (Perfect Forwarding),一言以蔽之,就是确保参数在传递过程中,原有的属性限定符保持不变。

少说废话,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void operation(const Type& value) {
std::cout << "const Type&\t\t" << value << std::endl;
}

void operation(Type& value) {
std::cout << "Type&\t\t" << value << std::endl;
}

void operation(Type&& value) {
std::cout << "Type&\t\t" << value << std::endl;
}

// Forwarding
void forward(const Type& value) {
operation(value);
}

void forward(Type& value) {
operation(value);
}

void forward(Type&& value) {
operation(std::move(value));
}

在上述代码中,定义了两个函数(只看函数名而不是函数签名的话)operation 以及 forward,后者仅仅是将参数原封不动地传给前者。需要注意的是 forward 的第三个函数重载,这里用了 std::move 函数,作用是将左值转换成右值,因为虽然其看上去是右值引用 Type&&,但实际上是 Type& 这个左值。

如果要用函数模板来实现的话虽然可以写成这个形式:

1
2
3
4
template<typename T>
void forward(T value) {
operation(value);
}

但是对于右值引用却没法很好地转发,因此 C++11 起可以写成以下形式以同时支持上述三种参数类型:

1
2
3
4
template<typename T>
void forward(T&& value) {
operation(std::forward<T>(value));
}

上面的 T&& 称为转发引用 (forwarding reference, C++17 标准规定的术语) 或者是 万能引用 (universal reference),但是需要注意,这里的 T 是待推导的类型而不是具体的类型,比如 int&& 就不是转发引用。

Compile-Time Programming

Basic Template Terminology

Generic Libraries

Templates in Depth

Templates and Design


C++ 模板元编程笔记
https://devexzh.github.io/2023/Note_Of_Cplusplus_Template_Metaprogramming/
作者
Ryker Zhu
发布于
2023年7月17日
许可协议