1 引入模板
实现一个算法:返回两个变量中的较大的一个,并支持不同的数据类型。如果不使用C++模板,会有哪些选择呢?
根据数据类型一遍一遍实现算法
int max_int(int x, int y) {return x > y ? x : y;
}int max_double(double x, double y) {return x > y ? x : y;
}
使用void *指针,然后指定比较函数
typedef const void *(*cmp)(const void *, const void *);const void *max_cmp(const void *x, const void *y, cmp f)
{return f(x, y);
}const void *cmp_int(const void *x, const void *y) {const int *ix = (const int *)x;const int *iy = (const int *)y;return *ix > *iy ? x : y;
}const void *cmp_double(const void *x, const void *y) {const double *dx = (const double *)x;const double *dy = (const double *)y;return *dx > *dy ? x : y;
}
使用继承,定义一个接口类,由派生类实现比较函数
class base
{
public:virtual bool max_than(const base &rhs) const = 0;
};class I : public base {
public:I(int i) : m_data(i) {}virtual bool max_than(const base &rhs) const {const I &other = dynamic_cast<const I &>(rhs);return m_data > other.m_data;}public:int m_data;
};class D : public base {
public:D(int d) : m_data(d) {}virtual bool max_than(const base &rhs) const {const D &other = dynamic_cast<const D &>(rhs);return m_data > other.m_data;}public:double m_data;
};const base &max_class(const base &x, const base &y) {return x.max_than(y) ? x : y;
}
仔细观察,会发现三种方案都存在不同程度的冗余。
方案一冗余程度最高,没有任何复用可言。这种方案不仅增加了工作量,还增加了维护成本。每当需要支持一种新的数据类型,都需要重新实现一遍算法;如果算法更新,还需要同步到各个实现。
方案二和方案三的本质是一样的,对原有数据类型进行封装,通过回调方式解决数据类型差异问题。不同之处在于方案二使用void指针实现,存在严重的类型安全问题(但这也是C语言中的唯一选择);方案使用继承,通过基类引用取代了void指针。
通过与上面这些方案对比,使用模板实现这个算法就简单的多了,如下:
#include <cstdio>
#include <string>
#include <string>template <typename T>
T max_(T x, T y)
{return x > y ? x : y;
}int main(int argc, char **argv)
{printf("%d\n", max_(10, 20));printf("%f\n", max_(10.1, 20.2));std::string s1("a");std::string s2("b");printf("%s\n", max_(s1, s2).c_str());return 0;
}
2 模板的本质
模板的本质是一种c++多态,又称编译时多态。尽管源码中仅有一个函数,但却实现了对多种类型的支持。为什么会有如此神奇的事情?因为C++编译器有一个神奇的技能——无中生有。通过“objdump --syms template | grep max_”查看可执行程序的符号表,程序中确实包含三个名称包含max_的函数,如下:
可执行程序中的三个函数,与源码中的max_函数是什么关系呢?不妨做个实验。
实验一数据类型为double的调用实例,发现可执行程序中名称包含max_的函数少了一个;
#include <cstdio>
#include <string>
#include <string>template <typename T>
T max_(T x, T y)
{return x > y ? x : y;
}int main(int argc, char **argv)
{printf("%d\n", max_(10, 20));std::string s1("a");std::string s2("b");printf("%s\n", max_(s1, s2).c_str());return 0;
}
增加一行数据类型为int的调用实例,发现可执行程序中名称包含max_的函数并未增加;
#include <cstdio>
#include <string>
#include <string>template <typename T>
T max_(T x, T y)
{return x > y ? x : y;
}int main(int argc, char **argv)
{printf("%d\n", max_(10, 20));printf("%d\n", max_(50, 10));std::string s1("a");std::string s2("b");printf("%s\n", max_(s1, s2).c_str());return 0;
}
从上面的实验推出,名称中包含max_的函数的生成,仅与实例的类型相关,与调用次数无关。但现在还不能推出可执行程序名称包含max_函数,就是源码中的max_函数。
再做一个实验,使用“g++ -S template.cpp -o template.s”生成汇编代码,仔细研究template.s(代码太长,此处不在粘贴),发现可执行程序名称包含max_函数,就是源码中的max_函数。在代码的编译阶段,g++为max生成了三个不同的实例版本。
由此可见,模板也并非免费的午餐:增加编译时间;增加生成程序的体积。但与其所提升的开发效率相比,这些可以忽略不计。