From a86fa7ee324e622c47c749d0f1b5387a9d69ee0c Mon Sep 17 00:00:00 2001 From: Ye WU Date: Sat, 1 Mar 2014 00:06:21 -0800 Subject: [PATCH] =?UTF-8?q?2.1=E8=8A=82=E5=86=99=E4=BD=9C=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index ea62313..9bfe836 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -579,6 +579,87 @@ public: 这个技术的名字,并不叫“模板”,而是叫“元编程”。 +元(meta)无论在中文还是英文里,都是个很“抽象(abstract)”的词。因为它的本意就是“抽象”。元编程,也可以说就是“编程的抽象”。用更好理解的说法,元编程意味着你撰写一段程序A,程序A会运行后生成另外一个程序B,程序B才是真正实现功能的程序。那么这个时候程序A可以称作程序B的元程序,撰写程序A的过程,就称之为“元编程”。 + +回到我们的堆栈的例子。真正执行功能的,其实仍然是浮点的堆栈、整数的堆栈、各种你所需要的类型的堆栈。但是因为这些堆栈之间太相似了,仅仅有着些微的不同,我们为什么不能有一个将相似之处囊括起来,同时又能分别体现出不同之处的程序呢?很多语言都提供了这样的机会。C中的宏,C++中的模板,Python中的Duck Typing,广义上将都能够实现我们的思路。 + +我们的目的,是找出程序之间的相似性,进行“元编程”。而在C++中,元编程的手段,可以是宏,也可以是模板。 + +宏的例子姑且不论,我们来看一看模板: + +``` C++ +template +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 StackInt; +typedef Stack 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.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承