返回

多态

起因

在实现我的离线渲染器中,用到了大量oop(其实就是基类然后继承)

我有一个Shape基类,里面接口全声明为标准的纯虚函数接口

virtual TYPE FUNCTION() const = 0;

当你继承这个类后,继承类必须实现这个接口,不然编译报错

一开始还没有写实现,但类已经继承了,没办法,在接口开个洞(真难看啊

virtual TYPE FUNCTION() const {}

于是我基于 C++20 标准库的source_location写了一个

[[noreturn]]
inline void unimplemented(const std::source_location location = std::source_location::current())
{
    std::cerr << '\n'
              << '['  << location.file_name()
              << ':'  << location.line()
              << "] " << location.function_name()
              << " unimplemented!\n";
    exit(-1);
}

还没有实现接口的时候留下unimplemented,然后运行时报错

source_location暂时只有clang不支持(clang对C++20支持太慢了


网上冲浪时看到有人说,接口虚函数实现动态多态是一种糟糕实践,优雅的做法是Existential Type,在C++社区称为sean parent polymorphism

详细可以看这个视频

John Bandela “Polymorphism != Virtual: Easy, Flexible Runtime Polymorphism Without Inheritance”

reddit上的关于该视频的讨论

什么是多态

特定多态

Ad hoc polymorphism

函数重载,当然运算符重载也一样

参数化多态

Parametric polymorphism

类似 C++ 的模板,也可以称为泛型

子类型多态

Subtyping

平时编程用的最多的,继承基类,调用相同符号的方法

Existential Type

中文语境下叫类型擦除/隐藏类型实现(机翻直译叫存在类型)

看一段 C++ 代码

#include <functional>
#include <vector>
#include <iostream>

// type Messenger = ∃ a. { x: a, Print: a -> string -> ⊥ }
struct Messenger {
    using QuantificationBound = auto(std::string_view)->void;
    std::function<QuantificationBound> f = {};

    Messenger() = default;
    Messenger(auto x) {
        f = [=](auto msg) { x.Print(msg); };
    }

    auto Print(auto msg) const {
        f(msg);
    }
};

struct A {
    auto Print(auto msg) const {
        std::cout << "A says " << msg << std::endl;
    }
};

struct B {
    auto Print(auto msg) const {
        std::cout << "B says " << msg << std::endl;
    }
};

auto main()->int {
    auto x = std::vector<Messenger>{ A{}, B{} };
    for (auto y : x)
        y.Print("hi");
}

我觉得有点像上面所说的子类型

在代码中,用std::function封装了真正的调用函数,而不需要考虑具体类型std::function除了保存函数状态,还有运行时多态。但在不同 stl 的实现,有些是直接用 virtual,有些手动用指针打虚表。虽然看起来底层实现一样,不过好处就是,不需要给类函数加上 virtual,这算是一种解耦

但用std::function实现其实有一些缺陷,比如将 lambda 函数赋值给它的时候,lambda capture 的变量太多会动态分配内存。比较推荐的是用 template

Reference

  1. Existential type 是什么?
  2. 多态都不知道,谈什么对象
  3. 关于std function和lambda function的性能调试
Licensed under CC BY-NC-SA 4.0
最后更新于 Sep 11, 2022