0%

Modern C++中多态的实现方法(union,virtual,variant)

最近做服务器的项目的时候,在优化自己的架构,抽象出了一个适配器层,服务器将借由这个适配器的抽象来到达上层的用户接受消息层。而适配器层所负责的就是建立连接,不管是tcp的epoll模式连接,还是rdma编程模式下的建立连接,我们都希望借助抽象出适配器的子类派生出来实现,在这样的结构下,一是对代码做到了很好的简化作用,二是可以做到以后更深层次的扩容,架构的可扩展性比较好,以后也可以接入如kafka这样的消息中间件做异步的消息转发,提高服务器的程序吞吐量,三也是方便我写测试代码。

所以说这层抽象是很重要的。并且要实现这样的抽象类和派生类,我们就需要用到c++的多态。而我其实也是在学习modern C++的过程中了解到C++17引入了variant,也是学习了一下相关的知识,在这次博客我会记录下c++这三种不同的多态实现方式union,virtual和variant。在这里我也写一下对这三种多态方式做出的研究吧。

首先是最熟悉的virtual,C++编译器实现 “virtual” 的机制是通过在类的虚函数表(vtable)中存储虚函数的地址,并在对象中添加一个指向虚函数表的指针。当一个对象调用一个虚函数时,编译器会根据对象的类型和虚函数在虚函数表中的位置,找到对应的虚函数指针,然后调用该函数的实现代码。由于虚函数表是在编译期间创建的,因此可以在运行时根据对象的实际类型动态地调用相应的虚函数,实现多态性。
下面是一个简单的c++ virtual实现的多态示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

class Shape {
public:
virtual double area() const = 0;
};

class Rectangle : public Shape {
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
private:
double width, height;
};

class Circle : public Shape {
public:
Circle(double r) : radius(r) {}
double area() const override {
return 3.1415926 * radius * radius;
}
private:
double radius;
};

int main() {
Shape* s1 = new Rectangle(2.0, 3.0);
printf("area = %f\n",s1->area());
delete s1;
return 0;
}

可以看到,通过c++ virtual实现的多态代码非常的直观,但是这也毫无疑问带来了一定的缺点,主要的缺点就在于,纯虚函数是不能内联的,这是因为内联函数在编译时会直接将函数的代码插入到调用处,从而避免了函数调用的开销。但对于虚函数来说,由于它在运行时才确定调用的实际实现,即使你代码后已经对他进行了指针类型的强制转化,仍旧不能在编译时确定具体的函数实现,导致了他在运行时会去寻找相应的代码,从而影响程序的性能。因为希望做的是一个低延迟的交易系统,性能事实上是十分关键的因素。

第二种写法为类似c语言的写法,我们在c语言中实现多态的时候,就会使用union来实现,就像c版本tiger编译示例代码中所实现的那样,他的好处是代码程序的性能很高,但缺点就是程序的易读性不高,并且子类增加维护页非常麻烦,union无法自动处理构造和析构等逻辑,不能保证类型安全。

由此,C++17提出了std::variant,这是一种类似于联合体(union)的数据类型,它可以在同一块内存空间中存储多个不同的类型,但每次只能使用其中的一个成员。与联合体不同的是,std::variant 提供了安全和类型安全的访问方式,这是因为访问 std::variant 的成员是通过模板参数和类型匹配实现的,而不是像联合体一样使用显式的成员名称。同时,std::variant 还提供了许多方便的成员函数,如 std::get 和 std::visit 等,可以更方便地访问其中的成员。

C++ variant实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <variant>

class Rectangle {
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const {
return width * height;
}
private:
double width, height;
};

class Circle {
public:
Circle(double r) : radius(r) {}
double area() const {
return 3.1415926 * radius * radius;
}
private:
double radius;
};

using Shape = std::variant<Rectangle, Circle>;

double get_area(const Shape& shape) {
return std::visit([](const auto& s) { return s.area(); }, shape);
}

int main() {
Shape s1 = Rectangle(2.0, 3.0);
printf("area = %f",get_area(s1));
return 0;
}

从C++17发布出来的信息来看,他是能做到虚函数内联的。使用 std::variant 实现多态时,由于它是一个模板,编译器可以在编译期确定各个类型的大小和布局,因此可以使用内联方式实现,避免了虚函数和虚表的开销。