Dyno:运行时多态的正确之道

Dyno:运行时多态的正确之道

去发现同类优质开源项目:https://gitcode.com/

Travis 状态

请注意,这个库目前是实验性的,只是一种纯粹的好奇心产物。接口稳定性或实现质量不保证。使用需自行承担风险。

概览

Dyno 是一个比标准C++更好地解决运行时多态问题的库。它提供了一种非侵入式定义接口的方法,并且可以自定义存储多态对象和调用虚方法的方式。它无需继承、堆分配,甚至可以保持值语义,并且在性能上超过标准C++。

Dyno 实现了类似于 Rust 的trait对象、Go的接口、Haskell类型类和虚拟概念的功能,其核心技术是类型擦除,即 std::anystd::function 等实用类型的底层原理。

#include <dyno.hpp>
#include <iostream>
using namespace dyno::literals;

// 定义可绘制的对象接口
struct Drawable : decltype(dyno::requires_(
  "draw"_s = dyno::method<void (std::ostream&) const>
)) { };

// 描述具体类型如何实现该接口
template <typename T>
auto const dyno::default_concept_map<Drawable, T> = dyno::make_concept_map(
  "draw"_s = [](T const& self, std::ostream& out) { self.draw(out); }
);

// 定义可以持有任何可绘制对象的结构体
struct drawable {
  template <typename T>
  drawable(T x) : poly_{x} { }

  void draw(std::ostream& out) const
  { poly_.virtual_("draw"_s)(out); }

private:
  dyno::poly<Drawable> poly_;
};

struct Square {
  void draw(std::ostream& out) const { out << "Square"; }
};

struct Circle {
  void draw(std::ostream& out) const { out << "Circle"; }
};

void f(drawable const& d) {
  d.draw(std::cout);
}

int main() {
  f(Square{}); // 输出 Square
  f(Circle{}); // 输出 Circle
}

此外,如果觉得代码太冗长,还可以使用宏简化:

#include <dyno.hpp>
#include <iostream>

// 定义可绘制的对象接口
DYNO_INTERFACE(Drawable,
  (draw, void (std::ostream&) const)
);

struct Square {
  void draw(std::ostream& out) const { out << "Square"; }
};

struct Circle {
  void draw(std::ostream& out) const { out << "Circle"; }
};

void f(Drawable const& d) {
  d.draw(std::cout);
}

int main() {
  f(Square{}); // 输出 Square
  f(Circle{}); // 输出 Circle
}

编译器要求

这是一个C++17库,不支持旧版本编译器。已知与以下编译器兼容:

| 编译器 | 版本 | | ----------- |:--------:| | GCC | >= 7 | | Clang | >= 4.0 | | Apple Clang | >= 9.1 |

依赖项

该库依赖于 Boost.Hana 和 [Boost.CallableTraits]。单元测试依赖于 [libawful],基准测试依赖于 [Google Benchmark],[Boost.TypeErasure] 和 [Mpark.Variant],但仅用于库的本地开发。使用库本身不需要这些依赖。

库构建

Dyno 是一个头文件库,因此无需构建。只需将 include/ 目录添加到编译器的头文件搜索路径(确保依赖性已满足),即可直接使用。但是,仍然可以构建示例、单元测试和基准测试:

(cd dependencies && ./install.sh) # 安装依赖项
mkdir build
(cd build && cmake .. -DCMAKE_PREFIX_PATH="${PWD}/../dependencies/install") # 设置构建目录

cmake --build build --target examples   # 构建并运行示例
cmake --build build --target tests      # 构建并运行单元测试
cmake --build build --target check      # 运行示例和单元测试
cmake --build build --target benchmarks # 构建并运行基准测试

带来的变革

在编程中,处理具有共同接口但动态类型不同的对象的情况十分常见。C++通过继承来解决这一问题,但这种方法有一些缺点:

  1. 侵入性强
    SquareCircle为了实现Drawable接口,必须从Drawable基类派生。这意味着要修改这些类,限制了扩展性,例如无法让std::vector<int>实现Drawable接口。

  2. 与值语义不兼容
    继承要求通过指针或引用传递多态对象,这与语言和标准库的其他部分不符。例如,如何复制一个包含Drawable的向量?你需要提供一个虚拟的clone()方法,但这破坏了接口。

  3. 紧密耦合动态存储
    因为缺乏值语义,我们通常将多态对象存储在堆上。这既低效又在语义上错误,因为大多数情况下,栈上的存储就足够了。

  4. 阻止内联优化
    通过多态指针或引用来调用虚方法需要三次间接操作:加载对象中的vtable指针,加载vtable中的正确条目,最后是函数指针的间接调用。所有这些跳跃使编译器难以做出良好的内联决策。

然而,这就是C++为我们选择的方式,我们在使用动态多态时不得不接受这些规则。或者,真的是这样吗?

所以,这个库是什么呢?

Dyno 解决了C++的运行时多态问题,没有任何上述缺点,并且提供了更多优势:

  1. 非侵入性
    一个类型可以在不修改自身的情况下实现接口。甚至一个类型可以以不同方式实现同一个接口。有了Dyno,你可以告别无理的类层次结构。

  2. 基于值语义的100%
    多态对象可以直接传递,保留其自然的值语义。需要复制你的多态对象吗?当然,只要确保它们有一个拷贝构造函数。想要确保它们不会被复制吗?没问题,将其标记为删除。Dyno消除了愚蠢的clone()方法和API中指向的普遍性。

  3. 不绑定特定的存储策略
    多态对象的存储方式实际上是实现细节,不应干扰使用对象的方式。Dyno允许您完全控制对象的存储方式。如果你有很多小的多态对象,可以在局部缓冲区存储,避免任何分配。或者也许在堆上存储更有意义?当然可以去尝试。

  4. 灵活的调度机制,实现最佳性能
    存储vtable指针只是许多动态调度实现策略之一。Dyno为您提供完全控制动态调度的方式,实际上在某些情况下可以超越vtables。如果你在热点循环中有一个函数,可以将它直接存储在对象中,跳过vtable间接调用。您可以利用编译器永远不知道的应用程序特定知识优化一些动态调用——库级脱虚拟化。

使用库

首先,定义一个通用接口并为其命名。Dyno 提供了一个简单的领域专用语言(DSL)来完成这一点。例如,让我们定义一个名为Drawable的接口,表示可以绘制的类型:

#include <dyno.hpp>
using namespace dyno::literals;

struct Drawable : decltype(dyno::requires_(
  "draw"_s = dyno::method<void (std::ostream&) const>
)) { };

然后,创建满足该接口的类型。使用继承,你会这样做:

struct Square : Drawable {
  virtual void draw(std::ostream& out) const override final {
    out << "square" << std::endl;
  }
};

而在 Dyno 中,多态是非侵入式的,所以实现方式完全不同...

[Boost.CallableTraits]: https://github.com/eddyxu boost.callable_traits [libawful]: https://github.com/ldionne/libawful [Google Benchmark]: https://github.com/google/benchmark [Boost.TypeErasure]: https://www.boost.org/doc/libs/1_68_0/libs/type_erasure/doc/html/index.html [Mpark.Variant]: https://github.com/mpark/variant [Rust trait objects]: https://doc.rust-lang.org/book/ch10-02-traits.html [Go interfaces]: https://golang.org/ref/spec#Interface_types [Haskell type classes]: https://wiki.haskell.org/Type_classes [virtual concepts]: http://cpp-next.com/archive/2009/08/want-performance-write-virtual-concepts/ [type erasure]: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern [std::any]: https://en.cppreference.com/w/cpp/utility/any [std::function]: https://en.cppreference.com/w/cpp/utility/functional/function

去发现同类优质开源项目:https://gitcode.com/

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

戴艺音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值