【问题标题】:reinterpret_cast to know the type of the event at compilation timereinterpret_cast 在编译时知道事件的类型
【发布时间】:2021-04-13 15:05:28
【问题描述】:

我有一个基于事件的应用程序。 问题是我有很多不同类型的事件,有些课程听了很多。

目前,当我想接收事件时,我创建了一个以消息类型为参数的函数并订阅它。 我想要一个函数来接收所有事件,然后调用函数来处理事件。 这主要是为了更清楚地了解我的课程。 当然,我希望成本尽可能低,如果不是没有的话。

我已经找到了一种方法来做到这一点,我认为它不需要额外的成本,因为它应该在编译时处理。

我想确认是这种情况 我想知道这是否是一种好的做法或可能会给我带来问题的东西。

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

using namespace std;

enum class Type {
    Test, Test2
};

struct Test {
    vector<int>::iterator b;
    vector<int>::iterator e;
};

struct Test2 {
    int a = 0;
};

template<Type type>
void test(uintptr_t k) {
    if constexpr(type == Type::Test) {
        auto p = reinterpret_cast<Test*>(k);
        for (auto i = p->b; i != p->e; ++i) cout << *i << ' ';
        cout << '\n';
    } else if constexpr(type == Type::Test2) {
        auto p = reinterpret_cast<Test2*>(k);
        cout << p->a << '\n';
    }
}

int main() {
    Test2 v{10};
    auto a = reinterpret_cast<uintptr_t>(&v);
    std::function<void(uintptr_t)> f_display = test<Type::Test2>;
    f_display(a);

    return 0;
}

【问题讨论】:

  • 由于您需要显式提供测试类型,因此您不妨拥有一堆 test 重载,每个重载都有一个 Type::Test 类型。
  • v2 在哪里声明?还是应该是v
  • std::variant&lt;Test*, Test2*&gt;?
  • 发布的代码无法编译。没有v2v 不是指针。我猜你的意思是&amp;v2 并且你希望变量被称为v2
  • 以这种方式编写代码而不是使用重载函数或模板特化有什么意义?你可以让它test&lt;Test2&gt; 工作,你不需要reinterpret_cast

标签: c++ templates optimization


【解决方案1】:

您希望拥有一个处理不同类型事件的事件队列。我明白了,您可以在 C# 对象中看到这种东西,这些对象具有带有不同参数的 bajillion 自定义钩子;他们为所有人提供一个订阅系统,并进行强制转换以确保类型一致。

如果您想要一个中心瓶颈,我会这样做,以便您尽可能长时间地保持类型安全。

struct broadcaster {
  using callback = std::function<void(uintptr_t)>;
  using listener = std::weak_ptr<callback>;
  std::unordered_map<
    std::type_index,
    std::vector<listener>
 > listeners;

  template<class T, class F>
  std::shared_ptr<void> install_callback( F&& f ) {
    std::type_index index(typeid(T));
    auto cb = std::make_shared<callback>([f=std::forward<F>(f)](std::uintptr_t p){
      f( *reinterpret_cast<T*>(p) );
    });
    listeners[index].push_back( cb );
    return cb;
  }
  template<class T>
  std::size_t broadcast( T t ) {
    std::type_index index(typeid(T));
    auto it = listeners.find(index);
    if (it == listeners.end()) return 0;
    // remove uninstalled callbacks
    it->second.erase(
      std::remove_if(
        it->second.begin(), it->second.end(),
        [](auto&&wcb){return wcb.expired();}
      ),
      it->second.end()
    );
    // copy vector of callbacks for reentrancy purposes
    auto tmp = listeners.second;
    std::size_t retval = 0;
    for (auto&& wcb : tmp) {
      if (auto cb = wcb.lock()) {
        (*cb)( reintepret_cast<std::uintptr_t>(std::addressof(t)) );
        ++retval;
      }
    }
    return retval;
  }
};

我认为可以。

b.install_callback&lt;Type&gt;( some_function_taking_type ) 并取回 std::shared_ptr&lt;void&gt;。只要共享的 ptr 仍然有效,您就订阅了广播者。

有人可以通过b.broadcast&lt;Type&gt;( t ) 发送消息,这会调用注册到Type 且尚未过期的每个回调。它还会从映射中清除在调用 broadcast 之前过期的该类型的任何回调。

不过,这对你来说可能太多了。

这里的开销是在您进行广播时查找哈希表,然后您必须遍历一个向量并执行一些原子操作以查看回调是否在每个回调的基础上注册。

实际的回调有 1 层类型擦除(假设安装程序使用了裸 lambda,而不是已经包装在 std 函数中的)。

类型不安全被隔离到这个broadcaster 类;此类方法的输入和输出是类型安全的。

广播的东西种类数是开集。回调和广播者必须同意。

也许一种“修剪”方法可以清除未与之交互的悬空弱指针。

我在这里使用了我的共享 ptr“令牌”方法来处理注销回调。基本上,只要回调有效,回调安装程序就负责保留共享的 ptr。这使您可以让一个类安装一个侦听器并只存储一个共享 ptr,甚至它们的一个向量,并让 RAII 在销毁时清理它们。 (我发现悬空回调很痛苦)。

如果主播先死,没问题。如果听众先死,没问题。唯一可能的问题是,当监听器死亡并且没有清除广播器广播的回调令牌时,监听器没有处于适合监听的状态。这可以通过在不再想要监听时显式清除监听器中的令牌来解决(例如,在析构函数的第一步,或确保令牌存储是第一个成员,或其他)。

上面的代码没有编译,只是写的。它可能包含 tpyos。

【讨论】:

  • 谢谢,shared_ptr 真的有必要吗?
  • @antho 在订阅回调机制时,如何取消注册?如果它比单个函数调用更长,我发现 RAII 对于保持注销的正常运行非常有用。
  • 我使用在订阅时返回的唯一 ID。当对象不想再被订阅时,它会要求从事件中删除它的 id。我的回调被包装在一个带有 id 的类中,以便在向量中快速找到它。
  • @antho 当然;不是 RAII(因此需要编写代码来取消注册,而不仅仅是使用令牌生命周期),并且依赖于不超过广播者(或知道广播者的生命周期)的听众,但这种方法有效。共享 ptr 只是为了避免编写注销代码,并顺利处理“在迭代回调时注销”。
  • 实际上,我使用的是 RAII,但可能不如你干净。在碎纸机中,我删除了对象订阅的所有事件。
【解决方案2】:

我不会使用枚举来开启很长的路,而是使用已经提供调度功能的基本重载集,并且只在顶部添加强制转换部分:

void test_impl(Test *p) {
    for (auto i = p->b; i != p->e; ++i)
        std::cout << *i << ' ';
    std::cout << '\n';
}

void test_impl(Test2 *p) {
    std::cout << p->a << '\n';
}

template <class Type>
auto test(uintptr_t const k) {
    return test_impl(reinterpret_cast<Type *>(k));
};

See it live on Wandbox

【讨论】:

  • 你为什么在这里使用变量模板和 lambda,而一个简单的模板函数就足够了?
  • @NicolBolas 为什么,确实如此。我从一个返回 lambda 的函数模板开始,然后我会很聪明并使用一个变量来匹配 OP 的语法。怎么没想到这么简单的函数模板¯\_(ツ)_/¯
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-08
  • 1970-01-01
  • 1970-01-01
  • 2010-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多