mirror of
https://github.com/wuye9036/CppTemplateTutorial.git
synced 2024-03-22 13:11:16 +08:00
2.1节写作结束。
This commit is contained in:
parent
c3a9257a8a
commit
a86fa7ee32
81
ReadMe.md
81
ReadMe.md
|
@ -579,6 +579,87 @@ public:
|
||||||
|
|
||||||
这个技术的名字,并不叫“模板”,而是叫“元编程”。
|
这个技术的名字,并不叫“模板”,而是叫“元编程”。
|
||||||
|
|
||||||
|
元(meta)无论在中文还是英文里,都是个很“抽象(abstract)”的词。因为它的本意就是“抽象”。元编程,也可以说就是“编程的抽象”。用更好理解的说法,元编程意味着你撰写一段程序A,程序A会运行后生成另外一个程序B,程序B才是真正实现功能的程序。那么这个时候程序A可以称作程序B的元程序,撰写程序A的过程,就称之为“元编程”。
|
||||||
|
|
||||||
|
回到我们的堆栈的例子。真正执行功能的,其实仍然是浮点的堆栈、整数的堆栈、各种你所需要的类型的堆栈。但是因为这些堆栈之间太相似了,仅仅有着些微的不同,我们为什么不能有一个将相似之处囊括起来,同时又能分别体现出不同之处的程序呢?很多语言都提供了这样的机会。C中的宏,C++中的模板,Python中的Duck Typing,广义上将都能够实现我们的思路。
|
||||||
|
|
||||||
|
我们的目的,是找出程序之间的相似性,进行“元编程”。而在C++中,元编程的手段,可以是宏,也可以是模板。
|
||||||
|
|
||||||
|
宏的例子姑且不论,我们来看一看模板:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
template <typename T>
|
||||||
|
class Stack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void push(T v);
|
||||||
|
T pop();
|
||||||
|
Int Find(T x)
|
||||||
|
{
|
||||||
|
for(Int i = 0; i <= size; ++i)
|
||||||
|
{
|
||||||
|
if(data[i] == x) { return i; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ... 其他代码 ...
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Stack<int> StackInt;
|
||||||
|
typedef Stack<float> StackFloat;
|
||||||
|
```
|
||||||
|
|
||||||
|
通过模板,我们可以将形形色色的堆栈代码分为两个部分,一个部分是不变的接口,以及近乎相同的实现;另外一部分是元素的类型,它们是需要变化的。因此同函数类似,需要变化的部分,由模板参数来反应;不变的部分,则是模板内的代码。可以看到,使用模板的代码,要比不使用模板的代码简洁许多。
|
||||||
|
|
||||||
|
如果元编程中所有的变化的量(或者说元编程的参数),都是类型,那么这样的编程,我们有个特定的称呼,叫“泛型”。
|
||||||
|
|
||||||
|
但是你会问,模板的发明,仅仅是为了做和宏几乎一样的替换工作吗?可以说是,也可以说不是。一方面,很多时候模板就是为了替换类型,这个时候作用上其实和宏没什么区别。只是宏是基于文本的替换,被替换的文本本身没有任何语义。只有替换完成,编译器才能进行接下来的处理。而模板会在分析模板时以及实例化模板时时候都会进行检查,而且源代码中也能与调试符号一一对应,所以无论是编译时还是运行时,排错都相对简单。
|
||||||
|
|
||||||
|
但是模板也和宏有很大的不同,否则此文也就不能成立了。模板最大的不同在于它是“可以运算”的。我们来举一个例子,不过可能有点牵强。考虑我们要写一个向量逐分量乘法。只不过这个向量,它非常的大。所以为了保证速度,我们需要使用SIMD指令进行加速。假设我们有以下指令可以使用:
|
||||||
|
|
||||||
|
```
|
||||||
|
Int8,16: N/A
|
||||||
|
Int32 : VInt32Mul(int32 * 4, int32 * 4)
|
||||||
|
Int64 : VInt64Mul(int64 * 2, int64 * 2)
|
||||||
|
Float : VInt64Mul(float * 2, float * 2)
|
||||||
|
```
|
||||||
|
所以对于Int8和Int16,我们需要提升到Int32,而Int32和Int64,各自使用自己的指令。所以我们需要实现下的逻辑:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
for(v4a, v4b : vectorsA, vectorsB)
|
||||||
|
{
|
||||||
|
if type is Int8, Int16
|
||||||
|
VInt32Mul( ConvertToInt32(v4a), ConvertToInt32(v4b) )
|
||||||
|
elif type is Int32
|
||||||
|
VInt32Mul( v4a, v4b )
|
||||||
|
elif type is Float
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这里的问题就在于,如何根据 `type` 分别提供我们需要的实现?这里有两个难点。首先,if type == xxx 是不存在于C++中的。第二,即便存在根据 `type` 的分配方法,我们也不希望它在运行时branch,这样会变得很慢。我们希望它能按照类型直接就把代码编译好,就跟直接写的一样。
|
||||||
|
|
||||||
|
嗯,聪明你果然想到了,重载也可以解决这个问题。
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
GenericMul(int8 * 4, int8 * 4);
|
||||||
|
GenericMul(int16 * 4, int16 * 4);
|
||||||
|
GenericMul(int32 * 4, int32 * 4);
|
||||||
|
GenericMul(int64 * 4, int64 * 4);
|
||||||
|
// 其它 Generic Mul ...
|
||||||
|
|
||||||
|
for(v4a, v4b : vectorsA, vectorsB)
|
||||||
|
{
|
||||||
|
GenericMul(v4a, v4b);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
这样不就可以了吗?
|
||||||
|
|
||||||
|
唔,你赢了,是这样没错。但是问题是,我这个平台是你可没见过,它叫 `Deep Thought`, 特别缺心眼儿,不光有 `int8`,还有更奇怪的 `int9`, `int11`,以及可以代表世间万物的 `int42`。你总不能为之提供所有的重载吧?这简直就像你枚举了所有程序的输入,并为之提供了对应的输出一样。
|
||||||
|
|
||||||
|
好吧,我承认这个例子还是太牵强了。不过相信我,在你阅读完第二章和第三章之后,你会将这些特性自如地运用到你的程序之中。你的程序将会变成体现模板“可运算”威力的最好例子。
|
||||||
|
|
||||||
###2.2 模板世界的If-Then-Else:类模板的特化与偏特化
|
###2.2 模板世界的If-Then-Else:类模板的特化与偏特化
|
||||||
###2.3 函数模板的重载、参数匹配、特化与部分特化
|
###2.3 函数模板的重载、参数匹配、特化与部分特化
|
||||||
###2.4 技巧单元:模板与继承
|
###2.4 技巧单元:模板与继承
|
||||||
|
|
Loading…
Reference in New Issue
Block a user