因此,当我为我的游戏编程一些功能时,我遇到了一些我不理解的问题,我寻求解释。因此,我实现了一个简单的事件系统,允许我在代码中向不同的系统发送通信。一切都很好,直到我遇到了一个问题,将右值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;
}
字符串
特别是doSomething()函数中的em.notify(GameEvent::SomeEvent, "abc", 6);
行。
所以对于那个普通的“abc”字符串,当我尝试运行程序时,它到达这个事件处理函数,根据Visual Studio编译器,我得到desc
为null,它抛出一个异常。
所以我想,我将尝试从Somebody构造函数的lambda函数中删除const&
,并从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;
}
型
这给了我完全不同的错误,这是'写访问冲突'(或SIGSEGV根据godbolt编译器)。
我想知道,为什么它在这些情况下都不起作用?为什么这两种实现给予不同的错误?传递一个右值int,如图所示,不会给予任何错误,编译效果很好。
实际上,解决这个问题非常简单。我只需要声明一个std::string并像这样将其传递给eventManager.notify(...)
函数:
void doSomething()
{
std::string someString = "abc";
em.notify(GameEvent::SomeEvent, someString, 6);
}
型
它工作得很好。我只是想知道,为什么这个右值字符串不绑定到const&,为什么整数没有这样的问题,为什么我得到两个不同的错误。提前感谢解释。
1条答案
按热度按时间pgky5nke1#
问题是,当你调用
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
(这违反了你的设计)。溶液
这是一个你可以做的事情的粗略轮廓:
个字符
这种解决方案要好得多,因为它是类型安全的。在这种情况下,
GameEvent
总是需要在编译时知道。公平地说,你基本上总是知道你想要添加订阅者的游戏事件,或者你想要通知的事件,所以这不是一个真正的问题。用法
您可以使用这样的
EventManager
:型
进一步说明
理想情况下,您还可以使用
std::invocable
等方法为addSubscriber
和notify
添加适当的约束。否则,如果您混淆了任何涉及的类型,您将在emplace_back
或调用std::function::operator()()
时获得编译器错误。