C++拾趣——使用多态减少泛型带来的代码膨胀
Posted breaksoftware
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++拾趣——使用多态减少泛型带来的代码膨胀相关的知识,希望对你有一定的参考价值。
泛型编程是C++语言中一种非常重要的技术,它可以让我们大大减少相似代码编写量。有时候,我和同事提及该技术时,称它是“一种让编译器帮我们写代码的技术”。(转载请指明出于breaksoftware的csdn博客)
C++是一门静态语言,它最终的编译成果是可以直接运行于冯诺依曼体系的计算机上,而不像其他动态语言,可以运行于虚拟机等容器中。由于对运行效率得追求,C++也是一门类型精确的语言,即object是什么类型,在编译时往往就要确定好,这种方式可以称为数据的静态绑定。
template<class T>
void call_function(T& f)
f();
;
class PrintA
public:
virtual void operator ()()
std::cout << "Print A" << std::endl;
;
class PrintB : public PrintA
public:
virtual void operator ()()
std::cout << "Print B" << std::endl;
;
int main()
PrintA a;
PrintB b;
call_function(a);
call_function(b);
return 0;
上例中,我们只写了一个call_function模板函数,但是最终编译器会将其翻译成为两个独立的函数,分别是call_function<PrintA>()和call_function<PrintB>()。这也是之前所述“一种让编译器帮我们写代码的技术”的表现。
我们逆向上述代码来验证下
上图我们看到call_function<PrintA>()和call_function<PrintB>()方法的地址是不同的。这就意味着,这两个方法拥有各自的代码逻辑。再上升一个层次去看,使用call_function的模板方法的类有多少种,就会产生多少个相应的特化方法。我们只写了一个模板方法,但是编译器最终帮我们生成了多个,这个过程和现象我们称为发生了“代码膨胀”。
编译器将类型特化,即精确指定了类型,这就使得C++程序在运行时直接跳转到相应函数地址就行,而不需要做类型判别后去路由。这也是C++高效的一个重要原因。
除了静态绑定,C++还有半动态绑定。这也是C++实现多态的技术基础。我们可以使用该技术,部分的解决泛型技术带来的“代码膨胀”的问题。
以上例为例,我们只要增加如下方法就行
void call(PrintA& f)
f();
然后调用call(a)和call(b)
可以看到,两次调用的call方法指向了同一个地址。于是不管call方法操作的类型有多少个,它都没有导致代码的膨胀。
需要指出的是,泛型和多态在上例中,体现了“空间”和“时间”的选择问题。当我们在做优化代码时,往往最终会走到“时间换空间”或者“空间换时间”的选择中。
上例泛型技术,生成了多份函数。在调用时,方法对应的函数地址是确定的,于是这是种调用是高效的。这是“空间换时间”的案例。
上例多态技术,只生成了一份代码。在调用时,call方法需要找到object的虚表,然后计算出虚函数的地址,最后才能调用相应的虚函数。这个过程没有直接call一个地址快。这是“时间换空间”的案例。
目前CPU的发展已经进入瓶颈,磁盘的空间却越来越便宜。很多人可能觉得“空间换时间”是个更好的选择,其实不可以一概而论。因为如果程序的最终编译产物小,其在CPU指令缓存中发生了cache miss也可能变小,最终效率可能还是可观的。
以上是关于C++拾趣——使用多态减少泛型带来的代码膨胀的主要内容,如果未能解决你的问题,请参考以下文章
C++拾趣——STL容器的插入删除遍历和查找操作性能对比(Windows VirtualStudio)——删除
C++拾趣——STL容器的插入删除遍历和查找操作性能对比(ubuntu g++)——删除
C++拾趣——STL容器的插入删除遍历和查找操作性能对比(Windows VirtualStudio)——遍历和删除