mat*_*ush 2 c++ string templates type-deduction
因此,当我为游戏编写一些功能时,我遇到了一些我不理解的问题,我寻求解释。因此,我实现了一个简单的事件系统,允许我在代码中向不同的系统发送通信。一切工作正常,直到我遇到将右值 std::string 传递给我的函数之一的问题,请考虑以下代码:
#include <vector>
#include <functional>
#include <iostream>
#include <unordered_map>
#include <memory>
enum class GameEvent
{
SomeEvent,
OtherEvent
};
class EventManager
{
public:
template <typename ...Args>
using EventCallback = std::function<void(Args...)>;
template <typename ...Args, typename Callback>
void addSubscriber(const GameEvent& gameEvent, Callback&& callback);
template <typename ...Args>
void notify(const GameEvent& gameEvent, Args... args);
private:
struct ListenerBase
{
virtual ~ListenerBase() = default;
};
template <typename ...Args>
struct ListenerWrapper : public ListenerBase
{
EventCallback<Args...> callback;
template <typename Callback>
ListenerWrapper(Callback&& callback_) : callback(std::forward<Callback>(callback_)) {}
virtual ~ListenerWrapper() = default;
void useCallback(Args... args)
{
callback(args...);
}
};
private:
std::unordered_map<GameEvent, std::vector<std::shared_ptr<ListenerBase>>> eventSubscribers;
};
template<typename ...Args, typename Callback>
inline void EventManager::addSubscriber(const GameEvent& gameEvent, Callback&& callback)
{
eventSubscribers[gameEvent].emplace_back(std::make_shared<ListenerWrapper<Args...>>(std::forward<Callback>(callback)));
}
template<typename ...Args>
inline void EventManager::notify(const GameEvent& gameEvent, Args ...args)
{
auto it = eventSubscribers.find(gameEvent);
if (it == eventSubscribers.end())
return;
for (const auto& callback : it->second)
{
auto castedCallback = std::static_pointer_cast<ListenerWrapper<Args...>>(callback);
castedCallback->useCallback(args...);
}
}
class SomeClass
{
public:
SomeClass(EventManager& em) : em(em) {}
void doSomething()
{
em.notify(GameEvent::SomeEvent, "abc", 6);
}
EventManager& em;
};
class SomeListener
{
public:
SomeListener(EventManager& em)
{
em.addSubscriber<const std::string&, const int&>(GameEvent::SomeEvent,
[this](const std::string& desc, const int& nr){
someEventImpl(desc, nr);
});
}
void someEventImpl(const std::string& desc, const int& nr)
{
std::cout << "Desc: " << desc << " nr: " << nr << std::endl;
}
};
int main() {
EventManager manager;
SomeListener lstr(manager);
SomeClass scs(manager);
scs.doSomething();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
em.notify(GameEvent::SomeEvent, "abc", 6);特别是 doSomething() 函数中的这一行:
因此,使用这个简单的“abc”字符串,当我尝试运行程序并且它到达此事件处理函数时,
desc根据 Visual Studio 编译器,我得到的是 null,并且它抛出异常。
const&所以我想,我将尝试从 SomeListener 构造函数中的 lambda 函数中删除,并从someEventImpl 函数中删除,结果如下:
SomeListener(EventManager& em)
{
em.addSubscriber<std::string, const int&>(GameEvent::SomeEvent,
[this](std::string desc, const int& nr){
someEventImpl(desc, nr);
});
}
void someEventImpl(std::string desc, const int& nr)
{
std::cout << "Desc: " << desc << " nr: " << nr << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
这给了我完全不同的错误,即“写访问冲突”(或根据 godbolt 编译器的 SIGSEGV)。
我想知道,为什么它在这些情况下不起作用?为什么这两种实现都会给出不同的错误?如图所示传递右值 int 不会给出任何错误并且编译良好。
事实上,解决这个问题非常简单。我只需声明一个 std::string 并将其传递给函数,eventManager.notify(...)如下所示:
void doSomething()
{
std::string someString = "abc";
em.notify(GameEvent::SomeEvent, someString, 6);
}
Run Code Online (Sandbox Code Playgroud)
而且效果很好。我只是想知道,为什么这个右值字符串不绑定到 const&,为什么整数没有这样的问题以及为什么我收到两个不同的错误。预先感谢您的解释。
问题是当你打电话时em.notify(GameEvent::SomeEvent, "abc", 6),Args推导出来[const char*, int]。
ListenerWrapper<std::string const&, int const&>ListenerWrapper<Args...> = ListenerWrapper<const char*, int>这是未定义的行为,UBSan 捕获了这一点: https: //godbolt.org/z/WK4qWj3e1
你的设计非常脆弱。当您 时std::static_pointer_cast,您需要向下转换为您最初存储的类型。然而,你们没有采取任何措施来确保这一点。在目前的状态下,您的设计并不比std::any前后投射更好。你的多态性ListenerBase完全没用。
您需要以某种方式保证每种类型的事件都有一个特定的、硬编码的回调签名。隐式转换也应该有效,例如const char* -> std::string(这会破坏您的设计)。
这是您可以做的事情的粗略轮廓:
enum class GameEvent {
SomeEvent,
OtherEvent
};
// define a callback function type for each event
template <GameEvent Event>
using CallbackType =
std::conditional_t<Event == GameEvent::SomeEvent, void(std::string_view, int),
std::conditional_t<Event == GameEvent::OtherEvent, void(int),
void>>;
// helper type
template <GameEvent Event>
struct Callbacks {
using type = CallbackType<Event>;
std::vector<std::function<type>> callbacks;
};
Run Code Online (Sandbox Code Playgroud)
struct EventManager : Callbacks<GameEvent::SomeEvent>, Callbacks<GameEvent::OtherEvent> {
template <GameEvent Event, typename F>
void addSubscriber(F&& callback) {
Callbacks<Event>::callbacks.emplace_back(std::forward<F>(callback));
}
template <GameEvent Event, typename... Args>
void notify(Args... args) {
for (auto& f : Callbacks<Event>::callbacks) {
f(args...);
}
}
};
Run Code Online (Sandbox Code Playgroud)
这个解决方案要好得多,因为它是类型安全的。在这种情况下,GameEvent总是需要在编译时知道。公平地说,您基本上总是知道要向哪个游戏事件添加订阅者,或者要通知哪些事件,所以这并不是真正的问题。
你可以使用这样EventManager的:
struct EventManager : Callbacks<GameEvent::SomeEvent>, Callbacks<GameEvent::OtherEvent> {
template <GameEvent Event, typename F>
void addSubscriber(F&& callback) {
Callbacks<Event>::callbacks.emplace_back(std::forward<F>(callback));
}
template <GameEvent Event, typename... Args>
void notify(Args... args) {
for (auto& f : Callbacks<Event>::callbacks) {
f(args...);
}
}
};
Run Code Online (Sandbox Code Playgroud)
请参阅编译器资源管理器中的实时示例。
理想情况下,您还可以使用 et al 添加适当的约束std::invocable。到addSubscriber和notify。否则,如果您弄乱了涉及的任何类型,您将emplace_back在调用std::function::operator()().