专业的编程技术博客社区

网站首页 > 博客文章 正文

c++编译期多态——接上次CTAD随笔

baijin 2024-10-12 02:09:37 博客文章 13 ℃ 0 评论

头条小白,望大家多多关注。跪求关注,你的关注是对我最大的鼓励,关注看下期。

上次在CTAD中,提了一下此特性可以结合C++编译期多态使用,效果更好。那什么是编译期多态呢?

这我们可能要先简单介绍下运行期多态的概念了,运行期多态也是基本所有高级编程语言都支持的特性,类比Rust 中的trait, Java 中的interface, C++ 中的virtual 虚函数父类和子类继承关系等。

这里还是以C++为例,进行接下来的介绍,其它语言实现运行期多态的方式各有不同,如果大家感兴趣,请评论留言,最重要的关注点点啊,大兄弟。你想知道的这里都有。

C++运行期多态,其实是通过虚函数表实现的,如果一个C++类中存在virtual 关键字定义的函数,此类默认会存在一个指向虚函数表的指针。

如何证明这件事呢? 有个好办法,看看汇编那就一目了然了,俗话说的好,汇编之下没秘密嘛。

说整就整,看下图:

然后看下对应的汇编中,类定义的部分:

首先,上面代码是没意义的哈,如果开启-O优化,那么汇编都会被优化掉了,请忽略优化。

然后我们来分析下,简单的汇编,就很一目了然了。可以看到父类TestVirtualPointer 保存了两个字段,第一个就是vtable(所谓的虚函数表指针,保存了实际虚函数表的地址),然后第二个字段是typeinfo name(实际就是指向了一个类名字的字符串),ConcreteVirtualObj类似。然后ConcreteVirtualObj 实现了虚函数,vtable 指向了虚函数表头指针位置。

编译期多态,就是靠着父类型的指针和引用指向子类型的指针和引用时,由于vtable 是类的第一个字段,所以此时父类的指针或者引用指向的具体实例对象是什么类型,在调用虚函数时,就会从对应子类型的vtable中找到对应的调用函数,达到运行期多态的效果。

运行期多态通过在一定意义上的类型擦除,实现了多态,把子类的类型全部擦除为父类型,然后在运行期,实例化对象的vtable 是谁的,就调用谁的函数这样。

下面给个简单的示例:

struct Parent {
    virtual void print_name() {
        std::cout << "parent\n";
    }
};

struct Child : Parent {
    void print_name() override {
        std::cout << "child\n";
    }
};

int main() {
    Child child;
    Parent& parent = child; // 父类型的引用指向子类型,
  																		     //或者指针(智能指针)都行,没有区别,这里偷个懒
     parent.print_name(); 
    return 0;
}

运行后会打印 child 字段。

那么C++编译期多态是什么呢?

编译期多态也称为静态多态,在一些需要多态特性设计扩展,但是又涉及高性能计算等领域,甚至连一个虚函数表跳转的指令都对运行性能有影响,而不允许使用虚函数时,那么就可以结合模板函数实现此种类型的多态。

编译期多态的优点就在于没有运行时开销,无虚函数指针,无需在运行时,跳转虚函数表查找需要执行的虚函数。但是缺点是:

一 要兜着点用,众所周知,模板会造成代码膨胀。

二. 编译时间的增长,模板的使用会延长编译时间。

三. 代码复杂度和问题定位变难,模板使用,或者元编程出现问题,大家都懂的,一堆报错看不懂。

下面给个简单的例子,大家一起看一下:

template<class T>
concept Switch = requires (T t) {
    t.turn_on();
    t.turn_off();
}; // 定义一个concept 限定模板类型

/*
定义一个模板类,此处具体含义可
简单理解成一个控制器,可以控制
所有开关的开启和关闭。
*/
template<Switch T>
class Control {
private:
    T switcher_; // 此处的T 类型,就是编译期多态,
                 //根据构造时入参的不同,可以出现不同的调用结果
public:
    Control(T switcher) : switcher_(std::move(switcher)) {}
    void power_on() {
        switcher_.turn_on();
    }
    void power_off() {
        switcher_.turn_off();
    }
};

template<class T>
Control(T t) -> Control<T>; // 我们的老朋友,CTAD

/*
定义一个具体的开关类,如下,灯光开关
*/
class LightSwitcher {
public:
    void turn_on() {
        std::cout << "light on\n";
    }
    void turn_off() {
        std::cout << "light off\n";
    }
};

int main() {
    LightSwitcher light;
    Control ctrl(light); // 通过CTAD,不需要显示指定类型
    ctrl.power_on(); // 此处即可打印light on
    ctrl.power_off(); // 此处即可打印light off
    // 如上形成编译期多态,因为编译时,类型即可确定,调用关系也确定。
    return 0;

这玩意儿就比较复杂了,给大家简单解释一下上面的代码。

我们想做的东西是,定义一个抽象的控制器,可以自动调用传入的所有抽象开关类(Switcher)并调用抽象开关类Switcher 的 turn_on 和 turn_off 方法。

然后为了展示多态, 我们定义了一个具体的灯光开关类,传入控制器中(Control类),即可通过Control类代理执行灯光的开启和关闭。

执行结果如下图所示:

这就是一个简单的编译期多态的实现,如果后期扩展,只要是实现了turn_on 和 turn_off方法的类,都可以传入后,多态调用。

上面用到了一些C++ 较新的特性,如果是C++低版本,需要使用一种叫做SFINAE 的技术进行Concept 和requires 的等效替换。

大家如果对SFINAE 技术不熟悉,就点个关注,劳烦劳烦,下一期给大家详细介绍。

跪求关注,希望各位看官顺手一点,你的关注是对我的最大鼓励。。。。

跪求关注。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表