mirror of
https://github.com/wuye9036/CppTemplateTutorial.git
synced 2024-03-22 13:11:16 +08:00
Reclarified 术语 模板类 和 类模板 的正确使用
This commit is contained in:
parent
c0e945d764
commit
cd4232437e
50
ReadMe.md
50
ReadMe.md
@ -17,10 +17,10 @@
|
||||
- [2.2.1. “模板类”还是“类模板”](#221-模板类还是类模板)
|
||||
- [2.2.2. Class Template的与成员变量定义](#222-class-template的与成员变量定义)
|
||||
- [2.2.3. 模板的使用](#223-模板的使用)
|
||||
- [2.2.4. 模板类的成员函数定义](#224-模板类的成员函数定义)
|
||||
- [2.2.4. 类模板的成员函数定义](#224-类模板的成员函数定义)
|
||||
- [2.3. 函数模板 (Function Tempalte) 入门](#23-函数模板-function-tempalte-入门)
|
||||
- [2.3.1. Function Template 的声明和定义](#231-function-template-的声明和定义)
|
||||
- [2.3.2. 模板函数的使用](#232-模板函数的使用)
|
||||
- [2.3.1. 函数模板的声明和定义](#231-函数模板的声明和定义)
|
||||
- [2.3.2. 函数模板的使用](#232-函数模板的使用)
|
||||
- [2.4. 整型也可是Template参数](#24-整型也可是template参数)
|
||||
- [2.5. 模板形式与功能是统一的](#25-模板形式与功能是统一的)
|
||||
- [3. 模板元编程基础](#3-模板元编程基础)
|
||||
@ -73,7 +73,7 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言,
|
||||
* 使用过STL;
|
||||
* 熟悉一些常用的算法,以及递归等程序设计方法。
|
||||
|
||||
此外,尽管第一章会介绍一些Template的基本语法,但是还是会略显单薄。因此也希望读者能对C++ Template最基本语法形式有所了解和掌握;如果会编写基本的模板函数和模板类那就更好了。
|
||||
此外,尽管第一章会介绍一些Template的基本语法,但是还是会略显单薄。因此也希望读者能对C++ Template最基本语法形式有所了解和掌握;如果会编写基本的函数模板和类模板那就更好了。
|
||||
|
||||
诚如上节所述,本文并不是《C++ Templates》的简单重复,与《Modern C++ Design》交叠更少。从知识结构上,我建议大家可以先读本文,再阅读《C++ Templates》获取更丰富的语法与实现细节,以更进一步;《Modern C++ Design》除了元编程之外,还有很多的泛型编程示例,原则上泛型编程的部分与我所述的内容交叉不大,读者在读完1-3章了解模板的基本规则之后便可阅读《MCD》的相应章节;元编程部分(如Typelist)建议在阅读完本文之后再行阅读,或许会更易理解。
|
||||
|
||||
@ -166,7 +166,7 @@ void foo(int a);
|
||||
|
||||
在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 `T`。可以说,这个 `T`是模板的精髓,因为你可以通过指定模板实参,将T替换成你所需要的类型。
|
||||
|
||||
例如我们用`ClassA<int>`来实例化模板类ClassA,那么`ClassA<int>`可以等同于以下的定义:
|
||||
例如我们用`ClassA<int>`来实例化类模板ClassA,那么`ClassA<int>`可以等同于以下的定义:
|
||||
|
||||
``` C++
|
||||
// 注意:这并不是有效的C++语法,只是为了说明模板的作用
|
||||
@ -175,11 +175,11 @@ typedef class {
|
||||
} ClassA<int>;
|
||||
```
|
||||
|
||||
可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器模板类。
|
||||
可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器类模板。
|
||||
|
||||
### 2.2.3. 模板的使用
|
||||
|
||||
对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板类`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类:
|
||||
对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的类模板`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类:
|
||||
|
||||
```C++
|
||||
template <typename T>
|
||||
@ -208,14 +208,14 @@ intArray.push_back(5);
|
||||
floatArray.push_back(3.0f);
|
||||
```
|
||||
|
||||
变量定义的过程可以分成两步来看:第一步,`vector<int>`将`int`绑定到模板类`vector`上,获得了一个“普通的类`vector<int>`”;第二步通过“vector<int>”定义了一个变量。
|
||||
与“普通的类”不同,模板类是不能直接用来定义变量的。例如:
|
||||
变量定义的过程可以分成两步来看:第一步,`vector<int>`将`int`绑定到类模板`vector`上,获得了一个“普通的类`vector<int>`”;第二步通过“vector<int>”定义了一个变量。
|
||||
与“普通的类”不同,类模板是不能直接用来定义变量的 —— 毕竟它的名字是“模板”而不是“类”。例如:
|
||||
|
||||
```C++
|
||||
vector unknownVector; // 错误示例
|
||||
```
|
||||
|
||||
这样就是错误的。我们把通过类型绑定将模板类变成“普通的类”的过程,称之为模板实例化(Template Instantiate)。实例化的语法是:
|
||||
这样就是错误的。我们把通过类型绑定将类模板变成“普通的类”的过程,称之为模板实例化(Template Instantiate)。实例化的语法是:
|
||||
|
||||
```
|
||||
模板名 < 模板实参1 [,模板实参2,...] >
|
||||
@ -237,9 +237,9 @@ ClassB<int, float>
|
||||
当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。
|
||||
就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。
|
||||
|
||||
### 2.2.4. 模板类的成员函数定义
|
||||
### 2.2.4. 类模板的成员函数定义
|
||||
|
||||
由于C++11正式废弃“模板导出”这一特性,因此在模板类的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板类中的成员函数,通常都是以内联的方式实现。
|
||||
由于C++11正式废弃“模板导出”这一特性,因此在类模板的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的类模板中的成员函数,通常都是以内联的方式实现。
|
||||
例如:
|
||||
|
||||
``` C++
|
||||
@ -302,9 +302,9 @@ void vector<T> /*看起来像偏特化*/ ::clear() // 函数的实现放在这
|
||||
|
||||
## 2.3. 函数模板 (Function Tempalte) 入门
|
||||
|
||||
### 2.3.1. Function Template 的声明和定义
|
||||
### 2.3.1. 函数模板的声明和定义
|
||||
|
||||
模板函数的语法与模板类基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子
|
||||
函数模板的语法与类模板基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子
|
||||
|
||||
```C++
|
||||
template <typename T> void foo(T const& v);
|
||||
@ -342,7 +342,7 @@ template <typename T> void foo()
|
||||
举个例子:generic typed function ‘add’
|
||||
```
|
||||
|
||||
在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板类和模板函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。
|
||||
在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板化的类和模板化的函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。
|
||||
|
||||
如何才能克服这一问题,最终视模板如平坦代码呢?
|
||||
|
||||
@ -356,7 +356,7 @@ template <typename T> void foo()
|
||||
|
||||
3. 把解决方案用代码写出来。
|
||||
|
||||
4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化模板类,但实际上这样做是不可行的)?
|
||||
4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化类模板,但实际上这样做是不可行的)?
|
||||
|
||||
通过重复以上的练习,应该可以对模板的语法和含义都有所掌握。如果提出问题本身有困难,或许下面这个经典案例可以作为你思考的开始:
|
||||
|
||||
@ -366,7 +366,7 @@ template <typename T> void foo()
|
||||
|
||||
当然和“设计模式”一样,模板在实际应用中,也会有一些固定的需求和解决方案。比较常见的场景包括:泛型(最基本的用法)、通过类型获得相应的信息(型别萃取)、编译期间的计算、类型间的推导和变换(从一个类型变换成另外一个类型,比如boost::function)。这些本文在以后的章节中会陆续介绍。
|
||||
|
||||
### 2.3.2. 模板函数的使用
|
||||
### 2.3.2. 函数模板的使用
|
||||
|
||||
我们先来看一个简单的函数模板,两个数相加:
|
||||
|
||||
@ -465,7 +465,7 @@ int b = GetValue<int>(1);
|
||||
|
||||
嗯,是不是so easy啊?嗯,你又信心满满的做了一个练习:
|
||||
|
||||
你要写一个模板函数叫 `c_style_cast`,顾名思义,执行的是C风格的转换。然后出于方便起见,你希望它能和 `static_cast` 这样的内置转换有同样的写法。于是你写了一个use case。
|
||||
你要写一个函数模板叫 `c_style_cast`,顾名思义,执行的是C风格的转换。然后出于方便起见,你希望它能和 `static_cast` 这样的内置转换有同样的写法。于是你写了一个use case。
|
||||
|
||||
``` C++
|
||||
DstT dest = c_style_cast<DstT>(src);
|
||||
@ -1151,7 +1151,7 @@ void PrintID()
|
||||
{
|
||||
cout << "ID of float: " << TypeToID<float>::ID << endl; // Print "1"
|
||||
cout << "NotID of float: " << TypeToID<float>::NotID << endl; // Error! TypeToID<float>使用的特化的类,这个类的实现没有NotID这个成员。
|
||||
cout << "ID of double: " << TypeToID<double>::ID << endl; // Error! TypeToID<double>是由模板类实例化出来的,它只有NotID,没有ID这个成员。
|
||||
cout << "ID of double: " << TypeToID<double>::ID << endl; // Error! TypeToID<double>是由类模板实例化出来的,它只有NotID,没有ID这个成员。
|
||||
}
|
||||
```
|
||||
|
||||
@ -1491,7 +1491,7 @@ template <typename T> struct X {
|
||||
|
||||
接下来,我们就来解决2.3.1节中留下的几个问题。
|
||||
|
||||
先看第四个问题。为什么MSVC中,模板函数的定义内不管填什么编译器都不报错?因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去,这是C++语法/语义分析的特殊性导致的。
|
||||
先看第四个问题。为什么MSVC中,函数模板的定义内不管填什么编译器都不报错?因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去,这是C++语法/语义分析的特殊性导致的。
|
||||
C++是个非常复杂的语言,以至于它的编译器,不可能通过词法-语法-语义多趟分析清晰分割,因为它的语义将会直接干扰到语法:
|
||||
|
||||
```C++
|
||||
@ -1611,7 +1611,7 @@ error: variable has incomplete type 'A'
|
||||
1 error generated.
|
||||
```
|
||||
|
||||
符合标准的写法需要将模板类的定义,和模板函数的定义分离开:
|
||||
符合标准的写法需要将类模板的定义,和函数模板的定义分离开:
|
||||
|
||||
> TODO 此处例子不够恰当,并且描述有歧义。需要在未来版本中修订。
|
||||
|
||||
@ -2463,11 +2463,11 @@ void inc_counter(ICounter& counterObj);
|
||||
|
||||
嗯,你说的没错,在这里这个特性一点都没用。
|
||||
|
||||
这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能性:
|
||||
这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能的替代方案:
|
||||
|
||||
* 重载(对模板函数)
|
||||
* 重载(适用于函数模板)
|
||||
|
||||
* 偏特化(对模板类而言)
|
||||
* 偏特化(适用于类模板)
|
||||
|
||||
* 虚函数
|
||||
|
||||
@ -2680,7 +2680,7 @@ void inc_counter(T& cnt);
|
||||
```
|
||||
|
||||
答案是:不能。
|
||||
因为`requires`作为关键字/保留字是存在二义性的。当它用于模板函数或者模板类的声明时,它是一个constraint,后面需要跟着concept表达式;而用于concept中,则是一个required expression,用于concept的求解。既然constraint后面跟着一个concept表达式,而requires也可以用来定义一个concept expression,那么一个风骚的想法形成了:我能不能用 `requires (requires (T t) {++t;})` 来约束模板函数的类型呢?
|
||||
因为`requires`作为关键字/保留字是存在二义性的。当它用于函数模板或者类模板的声明时,它是一个constraint,后面需要跟着concept表达式;而用于concept中,则是一个required expression,用于concept的求解。既然constraint后面跟着一个concept表达式,而requires也可以用来定义一个concept expression,那么一个风骚的想法形成了:我能不能用 `requires (requires (T t) {++t;})` 来约束模板参数呢?
|
||||
|
||||
当然是可以的!C++就是这么的简(~~有~~)单(~~病~~)!
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user