跟我学C++中级篇—虚函数的优化

一、多态

学习C++编程的开发者可能在一开始就明白了面向对象编程的三大特征即"封装、继承和多态"。多态的实现机制有很多种,这个在前面分析过。一般开发者认为的动态多态就是通过虚拟函数来实现的。通过指针或引用,动态的调用具体的类型的虚拟函数来实现相关的功能。对于代码的灵活性和扩展性有不小的提高。

二、虚函数的机制简述

作为运行时多态进行动态绑定的核心机制,虚函数的核心就在虚表机制。其实,很多面试也多是围绕着虚函数表进行各种的问来问去。

所谓虚表,就是每个类中如果存在着虚函数,一般会有一个隐藏的虚函数表,它主要用来存储所有虚函数的地址。这个表通常会定义在类对象的开始,它会隐式的包含一个虚表的指针vptr,通过其可以来访问类中的虚函数表。当一个继承类中重写了虚函数,那么表对应的位置将会进行相关的修改;同时如果增加了相关的虚函数,则会在表中进行增加(注意,纯虚函数会有标记无法被直接调用)。

而在实际运行过程中,要想真正找到动态绑定执行的函数,就必须在虚表内进行查询,找到相关的函数地址再进行运行。

三、问题和优化方法

通过上面的简单分析是不是发现,虚函数需要一个内存地址存储和动态查找的执行过程。而这两个过程也是拖慢虚函数运行效率的重要原因。有问题就会有解决的方法,虽然使用了动态绑定技术,对性能和执行效率的影响可能会有很大影响,但正是如此,也才有优化的空间。

一般对于动态绑定优化的方法主要有:

  1. 减少动态绑定的使用
    就是尽量在实际开发,把不必要的动态绑定函数去除。减少应用机率
  2. 使用内联
    某些情况下,虚函数也是可以内联的
  3. 引用高版本的final和override关键字
    通过关键字final和override来显式的为编译器优化提供支持
  4. 编译期去除多态
    现代的编译器往往具有在满足条件的情况下去除多态的能力。编译时可以使用编译器优化选项进行处理
  5. 使用静态多态
    这种在前面分析过,可以使用CRTP或宏、模板等技术进行静态多态的处理。
  6. 引入局部性
    这种也好理解,让虚函数表成为热点,在Cache中使用,从而提高调用的效率

当然,最简单的方法还是不使用多态,完全解决问题。但实际的情况可能千差万别,不可能开发者想怎么做就怎么做。

四、例程

下面看一个例子:

c 复制代码
#include <iostream>
#include <memory>
#include <vector>
constexpr double PI = 3.14159;
// 普通虚函数
struct Shape {
  virtual ~Shape() = default;
  virtual double area() const = 0;
};

struct Circle : Shape {
  double r;

  explicit Circle(double r) : r(r) {}

  double area() const override { return PI * r * r; }
};

struct Rect : Shape {
  double w, h;

  Rect(double w, double h) : w(w), h(h) {}

  double area() const override { return w * h; }
};

double sumV(const std::vector<std::unique_ptr<Shape>> &shapes) {
  double sum = 0;
  for (const auto &s : shapes) {
    sum += s->area(); // 虚调用
  }
  return sum;
}
// final去虚化
struct FinalCircle final : Shape {
  double r;

  explicit FinalCircle(double r) : r(r) {}

  double area() const override { return PI * r * r; }
};

double finalCall(const FinalCircle &c) { return c.area(); }

// CRTP:静态多态
template <typename Derived> struct StaticShape {
  double area() const { return static_cast<const Derived *>(this)->areaCacl(); }
};

struct StaticCircle : StaticShape<StaticCircle> {
  double r;

  explicit StaticCircle(double r) : r(r) {}

  double areaCacl() const { return PI * r * r; }
};

struct StaticRect : StaticShape<StaticRect> {
  double w, h;

  StaticRect(double w, double h) : w(w), h(h) {}

  double areaCacl() const { return w * h; }
};

template <typename ShapeType> double sumS(const std::vector<ShapeType> &shapes) {
  double sum = 0;
  for (const auto &s : shapes) {
    sum += s.area();
  }
  return sum;
}

int main() {
  std::vector<std::unique_ptr<Shape>> shapes;
  shapes.emplace_back(std::make_unique<Circle>(1.0));
  shapes.emplace_back(std::make_unique<Rect>(3.2, 2.0));

  std::cout << "virtual: " << sumV(shapes) << std::endl;

  FinalCircle fc(2.0);
  std::cout << "final: " << finalCall(fc) << std::endl;

  std::vector<StaticCircle> staticCircles;
  staticCircles.emplace_back(2.2);
  staticCircles.emplace_back(3.3);

  std::cout << "CRTP: " << sumS(staticCircles) << std::endl;
}

编译时可以采用下面的优化选项(需要根据情况处理):

复制代码
 g++ -O2 -std=c++17 main.cpp

或:

复制代码
  g++ -O3 -flto -std=c++17 main.cpp

五、总结

在"虚函数的性能"和"内联"中,对虚函数具体的情况进行了分析。特别是哪些情况会真正的拉开虚拟函数执行的效率。虚函数不是效率执行的必然的"杀手",但当它成为"杀手"时,应该能够从上面的优化中找出适应自己开发场景的方式来提高执行速度。

相关推荐
旖-旎11 分钟前
LeetCode 494:目标和(动态规划/01背包问题)—— 题解
c++·算法·leetcode·动态规划·01背包
ysa0510301 小时前
【板子】德州扑克模拟
c++·笔记·板子
慧都小项1 小时前
C/C++ 静态分析为什么要前移?Parasoft C/C++test 在 IDE 与 CI/CD 中的应用
c语言·c++·ide·功能测试·ai
qq_33776320192 小时前
国际期货资管系统开发方案|内外盘交易平台源码授权与二次开发支持
java·c语言·开发语言·c++·c#
zmzb010313 小时前
C++课后习题训练记录Day157
开发语言·c++
从零开始的代码生活_16 小时前
C++ 多态详解:虚函数、动态绑定、抽象类与虚表原理
开发语言·c++·后端·学习·算法
大彼方..17 小时前
C++ STL Vector 深度剖析:从内存管理到性能优化
开发语言·c++
炸薯条!17 小时前
从零开始学C++ (内存管理)
java·jvm·c++
cpp_250119 小时前
P6625 [省选联考 2020 B 卷] 卡牌游戏
数据结构·c++·算法·前缀和·贪心·洛谷题解·省选
旖-旎1 天前
《LeetCode 416 分割等和子集》
c++·算法·leetcode·动态规划·背包问题