C++17 中的通用工厂机制

2023-12-03

我想为一组派生类实现一个通用工厂机制,它不仅允许我通用地实现工厂函数来创建该类的对象,而且还允许其他模板类的创建者将派生类之一作为模板参数。

理想情况下,解决方案将仅使用 C++17 功能(无依赖项)。

考虑这个例子

#include <iostream>
#include <string>
#include <memory>

struct Foo {
    virtual ~Foo() = default;
    virtual void hello() = 0;
};

struct FooA: Foo { 
    static constexpr char const* name = "A";
    void hello() override { std::cout << "Hello " << name << std::endl; }
};

struct FooB: Foo { 
    static constexpr char const* name = "B";
    void hello() override { std::cout << "Hello " << name << std::endl; }
};

struct FooC: Foo { 
    static constexpr char const* name = "C";
    void hello() override { std::cout << "Hello " << name << std::endl; }
};

struct BarInterface {
    virtual ~BarInterface() = default;
    virtual void world() = 0;
};

template <class T>
struct Bar: BarInterface {
    void world() { std::cout << "World " << T::name << std::endl; }
};

std::unique_ptr<Foo> foo_factory(const std::string& name) {
    if (name == FooA::name) {
        return std::make_unique<FooA>();
    } else if (name == FooB::name) {
        return std::make_unique<FooB>();
    } else if (name == FooC::name) {
        return std::make_unique<FooC>();
    } else {
        return {};
    }
}

std::unique_ptr<BarInterface> bar_factory(const std::string& foo_name) {
    if (foo_name == FooA::name) {
        return std::make_unique<Bar<FooA>>();
    } else if (foo_name == FooB::name) {
        return std::make_unique<Bar<FooB>>();
    } else if (foo_name == FooC::name) {
        return std::make_unique<Bar<FooC>>();
    } else {
        return {};
    }
}

int main()
{
    auto foo = foo_factory("A");
    foo->hello();
    auto bar = bar_factory("C");
    bar->world();
}

run it

我正在寻找一种机制,可以让我同时实现这两个功能foo_factory and bar_factory不列出所有类,这样一旦我添加它们就不需要更新FooD作为附加的派生类。理想情况下,不同的 Foo 衍生品会以某种方式“自我注册”,但将它们全部列在一个中心位置也是可以接受的。

Edit:

根据评论/答案进行一些澄清:

  • 在我的例子中,有必要使用(类似的)字符串来调用工厂,因为工厂的调用者使用多态性Foo / BarInterface,即他们不知道具体的派生类。另一方面,在 Bar 中,我们希望使用派生 Foo 类的模板方法并促进内联,这就是为什么我们确实需要模板化派生类Bar类(而不是通过某些基类接口访问 Foo 对象)。
  • 我们可以假设所有派生的 Foo 类都定义在一个位置(因此,如有必要,可以接受手动注册,在同一位置将它们全部列出一次)。然而,他们不知道 Bar 的存在,事实上我们有多个不同的类,例如BarInterface and Bar。因此,我们不能像创建 Bar 的“构造函数对象”一样将它们保存在映射中。foo_factory。我认为需要的是所有派生 Foo 类型的某种“编译时映射”(或列表),这样在定义 bar_factory 时,编译器可以迭代它们,但我不知道该怎么做...

Edit2:

经证明相关的附加约束讨论期间:

  • 模板和模板模板:Foo 实际上是模板(具有单个类参数),而 Bar 是模板模板,采用具体的 Foo 作为模板参数。 Foo 模板没有专门化,并且都具有相同的“名称”,因此查询任何具体类型都可以。尤其SpecificFoo<double>::name始终有效。 @Julius 的答案已经得到扩展,以促进这一点。对于@Yakk 来说,同样可以做到(但我需要一些时间才能详细弄清楚)。
  • 灵活条码工厂代码:Bar 的工厂不仅仅只是调用构造函数。它还传递一些参数并进行一些类型转换(特别是,它可能有 Foo 引用,应该是dynamic_cast到相应的具体派生 Foo)。因此,允许在 bar_factory 定义期间内联编写此代码的解决方案对我来说似乎最具可读性。 @Julius' 的答案在这里效果很好,即使带有元组的循环代码有点冗长。
  • 使列出 Foos 的“单一位置”变得更加简单:从到目前为止的答案来看,我相信对我来说要走的路是拥有 foo 类型的编译时列表以及迭代它们的方法。有两个答案在一个中心位置定义 Foo 类型(或模板)列表(或者使用types模板或元组),这已经很棒了。然而,由于其他原因,我已经在同一个中心位置有一个宏调用列表,每个 foo 一个,例如DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...。可以申报吗FooTypes以某种方式利用这一点,所以我不必再次列出它们?我想这样的类型列表不能迭代地声明(附加到已经存在的列表),或者可以吗?如果没有的话,也许通过一些宏观魔法这是可能的。也许总是重新定义并因此附加到预处理器列表中DECLARE_FOO调用,最后是一些“迭代循环”来定义FooTypes类型列表。 IIRC boost 预处理器具有循环列表的功能(尽管我不想要 boost 依赖性)。

对于更多context,您可以将不同的 Foo 及其模板参数视为类似于以下的类Eigen::Matrix<Scalar>和 Bar 是与 Ceres 一起使用的成本函子。 bar 工厂返回类似的对象ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...> as ceres::CostFunction*指针。

Edit3:

根据@Julius的回答,我创建了一个与模板和模板模板一起使用的解决方案。我怀疑有人可以统一bar_tmpl_factory and bar_ttmpl_factory使用可变参数模板将其转换为一个函数(这是一回事吗?)。

run it

TODO:

  • combine bar_tmpl_factory and bar_ttmpl_factory
  • the point Making the "single place" listing the Foos even simpler from above
  • maybe replacing the use of tuples with @Yakk's types template (but in a way such that the creator function can be defined inline at the call site of the loop over all foo types).

我认为这个问题已经得到解答,如果有的话,以上几点应该是单独的问题。


template<class...Ts>struct types_t {};
template<class...Ts>constexpr types_t<Ts...> types{};

这让我们可以使用类型包,而无需元组的开销。

template<class T>
struct tag_t { using type=T;
  template<class...Ts>
  constexpr decltype(auto) operator()(Ts&&...ts)const {
    return T{}(std::forward<Ts>(ts)...);
  }
};
template<class T>
constexpr tag_t<T> tag{};

这让我们可以将类型作为值来使用。

现在,类型标记映射是一个接受类型标记并返回另一个类型标记的函数。

template<template<class...>class Z>
struct template_tag_map {
  template<class In>
  constexpr decltype(auto) operator()(In in_tag)const{
    return tag< Z< typename decltype(in_tag)::type > >;
  }
};

这需要一个模板类型映射并将其放入标签映射中。

template<class R=void, class Test, class Op, class T0 >
R type_switch( Test&&, Op&& op, T0&&t0 ) {
  return static_cast<R>(op(std::forward<T0>(t0)));
}

template<class R=void, class Test, class Op, class T0, class...Ts >
auto type_switch( Test&& test, Op&& op, T0&& t0, Ts&&...ts )
{
  if (test(t0)) return static_cast<R>(op(std::forward<T0>(t0)));
  return type_switch<R>( test, op, std::forward<Ts>(ts)... );
}

这让我们可以测试一堆类型的条件,并对“成功”的类型运行操作。

template<class R, class maker_map, class types>
struct named_factory_t;

template<class R, class maker_map, class...Ts>
struct named_factory_t<R, maker_map, types_t<Ts...>>
{
  template<class... Args>
  auto operator()( std::string_view sv, Args&&... args ) const {
    return type_switch<R>(
      [&sv](auto tag) { return decltype(tag)::type::name == sv; },
      [&](auto tag) { return maker_map{}(tag)(std::forward<Args>(args)...); },
      tag<Ts>...
    );
  }
};

现在我们想要创建一些模板类的共享指针。

struct shared_ptr_maker {
  template<class Tag>
  constexpr auto operator()(Tag ttag) {
    using T=typename decltype(ttag)::type;
    return [](auto&&...args){ return std::make_shared<T>(decltype(args)(args)...); };
  }
};

这样就使得共享指针有了一个类型。

template<class Second, class First>
struct compose {
  template<class...Args>
  constexpr decltype(auto) operator()(Args&&...args) const {
    return Second{}(First{}( std::forward<Args>(args)... ));
  }
};

现在我们可以在编译时组合函数对象。

接下来将其接线。

using Foos = types_t<FooA, FooB, FooC>;
constexpr named_factory_t<std::shared_ptr<Foo>, shared_ptr_maker, Foos> make_foos;

constexpr named_factory_t<std::shared_ptr<BarInterface>, compose< shared_ptr_maker, template_tag_map<Bar> >, Foos> make_bars;

and Done.

原来的设计其实是c++20用 lambda 代替那些structs for shared_ptr_maker等等。

Both make_foos and make_bars运行时状态为零。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++17 中的通用工厂机制 的相关文章

随机推荐

  • 最大并发 Socket.IO 连接数

    这个问题以前曾被问过 但最近没有被问过 也没有明确的答案 使用 Socket io 在需要添加另一台服务器之前 是否存在可以维持的最大并发连接数 有谁知道有哪些活跃的生产环境正在大规模使用 websockets 特别是 socket io
  • 如何在 ClickHouse 中向可执行 UDF 发送多个参数?

    我有一个输出输入的 python 脚本 usr bin python3 import sys if name main i 0 for line in sys stdin print i line end sys stdout flush
  • MySql REGEXP 运算符

    mySql REGEXP 运算符不区分大小写 该运算符是否有一个版本is区分大小写 Use the BINARY关键字 这迫使REGEXP将字符串匹配为二进制字符串 区分大小写 SELECT a REGEXP A a REGEXP BINA
  • Apache:禁用目录中的 php

    我想在我的服务器上的目录中禁用 php 我认为在 httpd conf 中设置 Options ExecCGI 会阻止执行 php 脚本 但显然我错了 所以 这就是我的 httpd conf 中的内容 它显然不起作用
  • ImportError:无法从“sklearn.base”导入名称“MultiOutputMixin”

    我只想用 scikit learn 进行线性回归 当我尝试导入线性模型包时 出现标题中的错误消息 我尝试遵循类似问题的解决方案 link 该建议基本上是删除请求 MultipleOutputMixin 的代码部分 当我这样做时 MultiO
  • 如何截取 YouTube 屏幕截图

  • 当用户关闭窗口选项卡时 JavaScript 发出警报

    我希望当用户关闭选项卡或窗口时 或者当他尝试移动到与我的站点不同的另一个位置时 弹出一个确认框 并且如果他确认执行 ajax 脚本 然后关闭或更改窗口 我不知道该怎么做 PS 我正在使用 jQuery window unload funct
  • 自定义 HtmlHelper 扩展方法在视图中不可用?

    我翻译了耶利米 克拉克的MVC 的复选框列表助手进入我的 VB Net 项目 但是当我尝试使用我认为的方法时 出现错误 CheckBoxList is not a member of System Web Mvc HtmlHelper Of
  • 如何以编程方式使折线图点处于活动/突出显示状态

    我使用的是 Chart js 2 0 beta2 页面和滑块上有几个折线图 我想突出显示每个折线图上与滑块位置匹配的数据点 它们都具有相同的点数 我不知道如何轻松地在代码中激活一个点 感谢您的任何提示 2 0 测试版解决方案 扩展您选择的图
  • 来自 JavaScript 的 JavaFX WebView 向上调用不起作用

    我有一个 JavaFX WebView 想要从 Web 视图中显示的 test html 调用 JavaBridge 类的方法 hello 为什么这不起作用 我确保 桥 对象仅在页面完全呈现时添加到 window object 因此这可能不
  • 能够在 print_r() 的输出中看到变量,但不确定如何在代码中访问它

    我用谷歌搜索 安装了 Devel Drupal for Firebug 但我找不到它 I found what我想要 我知道where这是 我只是不知道how为拿到它 为实现它 我将把它放在代码括号中 但 Devel 告诉我文件名 我想将其
  • 如何打印字符串中的每个字符?

    有没有一种方法可以一次从输入中读取一个字符并对其进行处理 而不需要对词汇进行标记 字符串上的 toCharArray 函数在这里可能很有用 for char c s toCharArray System out println c 并仅打印
  • 广播接收器作为 Android 中的内部类

    在我的代码中有一个扩展的内部类BroadcastReceiver 我已将以下行添加到AndroidManifest xml
  • 导入库项目时出现 NoClassDefFoundError

    我目前正在使用版本 4 6 1 Windows Vista Eclipse 编写一个单元测试项目 我的项目分为 3 部分 A 单元测试应用程序 类型 CLDC应用程序 B 待测试应用 类型 CLDC应用 C 一个库项目 类型 library
  • Portlet 中的 YUI 版本冲突问题

    我正在从 portlet 加载 yui js 3 3 0 版本文件 但 liferay 使用 3 2 0 yui js 文件 所以每当我加载该页面时 就会出现 js 错误 G ENV loaded VERSION 未定义 此错误出现在 yu
  • Struts2教程不起作用

    我正在学习struts2 我根据以下内容尝试我的 hello world 项目tutorial然后我在 eclipse 中运行代码 但这不起作用 控制台显示很多错误 第一个如下 com opensymphony xwork2 util lo
  • Iss 声明无效 Keycloak

    我使用 keycloak 服务登录我的网络应用程序 用作具有 oauth 2 0 安全性的后端 spring 当我使用从 keycloak 获得的不记名令牌向邮递员发出请求时 它给了我一个错误 401 并且也在旁边的答案文本中www Aut
  • 通过安全规则中的某个子值限制查询

    我正在努力想出构建部分数据库及其相关安全规则的最佳方法 我有聊天组 并且可以随时将用户添加到这些组中 当用户添加到组中时 他们应该只能检索发送的消息after那 他们不应该检索在他们 用户 添加到组之前发送的任何消息 我的第一种方法错误地假
  • 是否可以在离子侧边栏中使用幻灯片

    我正在用 ionic 构建一个移动应用程序 我想通过放置幻灯片来制作一个类似松弛的侧面菜单 例如 当您单击主菜单项时 它将像 slack 一样在侧菜单中滑出另一张幻灯片 我尝试在离子菜单中使用离子幻灯片 但幻灯片不起作用 请查看屏幕截图 这
  • C++17 中的通用工厂机制

    我想为一组派生类实现一个通用工厂机制 它不仅允许我通用地实现工厂函数来创建该类的对象 而且还允许其他模板类的创建者将派生类之一作为模板参数 理想情况下 解决方案将仅使用 C 17 功能 无依赖项 考虑这个例子 include