diff --git a/ReadMe.md b/ReadMe.md index 7de3ec8..942cfba 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -710,7 +710,7 @@ void doDiv(void* out, void const* data0, void const* data1, DATA_TYPE type) } ``` -在C++中比如在 `Boost.Any` 的实现中,运用了 `typeid` 来查询类型信息。和 `typeid` 同属于RTTI机制的 `dynamic_cast`,也经常会用来做类型判别的工作。我想你应该写过类似于下面的代码 +在C++中比如在 `Boost.Any` 的实现中,运用了 `typeid` 来查询类型信息。和 `typeid` 同属于RTTI机制的 `dynamic_cast`,也经常会用来做类型判别的工作。我想你应该写过类似于下面的代码: ``` C++ IAnimal* animal = GetAnimalFromSystem(); @@ -729,7 +729,48 @@ if(maybeCat) 当然,在实际的工作中,我们建议把需要 `dynamic_cast` 后执行的代码,尽量变成虚函数。不过这个已经是另外一个问题了。我们看到,不管是哪种方法都很难避免 `if` 的存在。而且因为输入数据的类型是模糊的,经常需要强制地、没有任何检查的转换成某个类型,因此很容易出错。 -模板与这些方法有区别吗? +但是模板与这些方法最大的区别并不在这里。模板无论其参数或者是类型,它都是一个编译期分派的办法。编译期就能确定的东西既可以做类型检查,编译器也能进行优化,砍掉任何不必要的代码执行路径。例如在上例中, + +``` C++ +template T addFloatOrMulInt(T a, T b); + +// 迷之代码1:用于T是float的情况 + +// 迷之代码2:用于T是int时的情况 +``` + +如果你运用了模板来实现,那么当传入两个不同类型的变量,或者不是 `int` 和 `float` 变量,编译器就会提示错误。但是如果使用了我们前述的 `Variant` 来实现,编译器可就管不了那么多了。但是,成也编译期,败也编译期。最严重的“缺点”,就是你没办法根据用户输入或者别的什么在运行期间可能发生变化的量来决定它产生、或执行什么代码。比如下面的代码段,它是不成立的。 + +``` C++ + +template +int foo() { return i + j; } +int main() +{ + cin >> x >> y; + return foo(); +} + +``` + +这点限制也粉碎了妄图用模板来包办工厂(Factory)甚至是反射的梦想。尽管在《Modern C++ Design》中(别问我为什么老举这本书,因为《C++ Templates》和《Generic Programming》我只是囫囵吞枣读过,基本不记得了)大量运用模板来简化工厂方法;同时C++11和14中的一些机制如Vardric Template更是让这一问题的解决更加彻底。但无论如何,光靠模板你就是写不出依靠类名或者ID变量产生类型实例的代码。 + +所以说,从能力上来看,模板能做的事情都是编译期完成的。编译期完成的意思就是,当你编译一个程序的时候,所有的量就都已经确定了。比如下面的这个例子: + +``` C++ +int a = 3, b = 5; +Variant aVar, bVar; +aVar.setInt(a); // 我们新加上的方法,怎么实现的无所谓,大家明白意思就行了。 +bVar.setInt(b); +Variant result = addFloatOrMulInt(aVar, bVar); +``` + +除非世界末日,否则这个例子里不管你怎么蹦跶,单看代码我们就能知道, `aVar` 和 `bVar` 都一定会是整数。所以如果有合适的机制,编译器就能知道此处的 `addFloatOrMulInt` 中只需要执行 `Int` 路径上的代码,而且编译器在此处也能单独为 `Int` 路径生成代码,从而去掉那个不必要的 `if`。 + +在模板代码中,这个“合适的机制”就是指“特化”和“部分特化(Partial Specialization)”,后者也叫“偏特化”。 + +####2.2.2模板原型与偏特化 + ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承