做玩游戏任务得q币的网站,网站按钮特效,靖宇东兴自助建站,wordpress如何改成中文字体一、虚函数性能
一般来说#xff0c;面向对象的设计中#xff0c;继承和多态是其中两个非常重要的特征。从使用的过程来看#xff0c;一般应用到继承的#xff0c;使用多态的可能性就非常大。而多态的实现有很多种#xff0c; 但开发者通常认为的多态#xff08;动多态面向对象的设计中继承和多态是其中两个非常重要的特征。从使用的过程来看一般应用到继承的使用多态的可能性就非常大。而多态的实现有很多种 但开发者通常认为的多态动多态一般是指通过虚函数来实现的多态。 虚函数的不同于普通函数它会通过一个虚表来控制函数的二次跳转或者叫做重定向。在普通的认知里虚函数这个特征一般是无法进行诸如内联等进行优化的。所以一谈到虚函数都会认为其性能堪忧。 但在前面的分析中也知道了什么东西都有特殊情况。但无论如何说明在常识里虚函数就是要比普通函数的性能要低。那么到底虚函数性能为什么会低是不是所有情况下都低下面进行一下分析。
二、用例子看问题
先看对比的例子
#include iostream
#include cmath
#include algorithm
#include iostream
#include chrono
class Parent
{
public:virtual double exeReadDataV(double a, int b){return std::sqrt(a) * std::sin(b);}double exeReadData(double a, int b){return std::sqrt(a) * std::sin(b);}
};int main()
{Parent* p new Parent();double a 3.14f;int b 3;double sum 0.f;auto t1 std::chrono::steady_clock::now();for (int num 0; num 100000000; num){//使用一个定值一个动态值sum p-exeReadDataV(a,num);//全是定值//sum p-exeReadDataV(a, b);}auto t2 std::chrono::steady_clock::now();auto escape1 t2 - t1;std::cerr escape1 is: escape1.count() std::endl;auto t3 std::chrono::steady_clock::now();for (int num 0; num 100000000; num){sum p-exeReadData(a,num);//sum p-exeReadData(a, b);}auto t4 std::chrono::steady_clock::now();auto escape2 t4 - t3;std::cerr escape2 is: escape2.count() std::endl;return 0;
}
执行结果
escape1 is:1982749665
escape2 is:1850111712从上面的执行可以看到两者的执行基本没区别。这可能打破了不少人的感官认知。无论哪种函数决定性能的有两个重要环节一个是调用的开销另外一个是确定性调用。前者比较好理解后者则不容易弄明白。其实可以这样理解一个写代码要尽量降低调用的开销一个是写出的代码编译器能更准确的知晓上下文然后进行优化。在前者确定的情况下后者就非常重要了。而虚函数被大多数人认为性能低的主要原因就在于后者。 虚函数需要一个虚表进行跳转在内存中这种开销与函数的功能开销相对来说可以忽略。但这种跳转本身意味着大量的未知而未知就意味着编译无法掌控更多的确定性而对某些很简单的优化可能都无法进行。正如早期的编译器在for循环中直接给一个变量和一个表达式效率差不少就是这个原因。而后的编译器则对此进行了优化将其直接转成一个常量值。把上面的例子中注释部分打开并注释当前的执行即两个参数都为定值的情况
执行结果
escape1 is:867604532
escape2 is:100431290在执行的函数调用中两个参数中一个为定值一个为变量时是否调用虚函数或者普通函数基本运行是差不多的。但是一旦调用的都为定值时此时普通函数可以直接将两个计算函数std::sqrt(a) 和 std::sin(b)均优化为固定值并只计算一次。此时再看计算结果可就差了将一个量级了。 而在前面的文章中“内联补遗”分析过的虚函数可以内联恰恰是那种可以明确确定的虚拟函数可以内联。即编译器知道虚函数不具有多态性的情况下它可普通函数没有什么区别。把原来的例子拿上来
class A{
public:inline virtual void Test(){...}
};
class B:public A
{
public:inline virtual void Test(){...}
};
inline void Get(A a){a.Test();
}
int main(){A a;B b;b.Test();//可以内联//下面不确定Get(b);Get(a);
return 0;
}
那么可以从内联的角度来分析虚函数为什么会给大家一个性能低的印象首先为什么内联函数快主要就是固定地址编译器优化两大方面。而上面的虚拟函数可以内联仿佛是与此结论相反但恰恰提到了能够内联的虚拟函数的情形。互相印证应该就明白为什么虚函数在多态的情况下性能低的原因了。 总之虚函数本身不是性能低的代表但是虚函数多态的调用会影响优化才是性能降低的根源。这些优化包括计算优化、分支跳转优化以及调用优化等等。而这些优化无法被编译器使用自然也就会使得编译出来的代码有着很多的多余的运行指令。
三、设计上对虚函数数的替代
如果对性能的敏感性不强那么如何使用虚函数不是一个多大的问题。可是如果实际情况对此要求比较严格时可以考虑用如下的方式来解决虚函数的使用问题 1、模板的方法如CRTP奇异递归 2、使用宏不推荐 3、通过设计模式等设计方法实现如访问者模式等 4、使用一些技术或方法绕开多态比如就直接写多个类然后直接控制 到底如何使用或不使用虚函数是根据实际情况来决定的。还是那句话没有一个技术是包打天下的。
四、总结
学习知识不是简单的为了会用而是能够灵活的运用。要想灵活的运用则必须掌握技术本质的内涵。只有把其内在体系掌握才能在具体的场景上发挥其优势。这也是总说的从必然世界到自由世界的一个哲学问题。