You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决C++事件系统中不同GameEvent派生类的std::function存储错误?

修复代码(基于原有架构)

1. 修正事件类的name初始化

首先确保每个派生类事件的name唯一,否则哈希表无法区分不同事件:

#pragma once

#include <string>

struct GameEvent
{
    std::string name = "Game Event";
};

struct StartEvent : GameEvent
{
    StartEvent() { name = "StartEvent"; } // 初始化唯一名称
    std::string msg = "This is the start event!";
};

struct EndEvent : GameEvent
{
    EndEvent() { name = "EndEvent"; } // 初始化唯一名称
    std::string msg = "This is the end event";
};

struct Events
{
    static const StartEvent s_StartEvent;
    static const EndEvent s_EndEvent;
};

const StartEvent Events::s_StartEvent;
const EndEvent Events::s_EndEvent;

struct GameEventHash
{
    std::size_t operator()(const GameEvent& evt) const
    {
        return std::hash<std::string>()(evt.name) << 1;
    }
};

struct GameEventEqual
{
    bool operator()(const GameEvent& t1, const GameEvent& t2) const {
        return t1.name == t2.name;
    }
};

2. 修改事件管理器,修复类型转换与切片问题

#pragma once

#include "events.h"

#include <functional>
#include <unordered_map>
#include <vector>
#include <type_traits>

struct EventManager
{
    template<typename T>
    static void AddListener(std::function<void(const T&)> listener);

    static void Broadcast(const GameEvent& evt);
private:
    // 修改function参数为const引用,避免切片
    static std::unordered_map<GameEvent, std::vector<std::function<void(const GameEvent&)>>, GameEventHash, GameEventEqual> s_Events;
};

inline void EventManager::Broadcast(const GameEvent& evt)
{
    // 先查找是否存在事件,避免[]自动创建空条目
    auto iter = s_Events.find(evt);
    if (iter != s_Events.end())
    {
        for (const auto& listener : iter->second)
        {
            listener(evt);
        }
    }
}

template<typename T>
void EventManager::AddListener(std::function<void(const T&)> listener)
{
    // 静态断言,确保T是GameEvent的派生类
    static_assert(std::is_base_of_v<GameEvent, T>, "T must inherit from GameEvent");

    T evt;
    // 包装listener,将GameEvent转换为T类型后调用
    auto wrappedListener = [listener](const GameEvent& baseEvt) {
        // 因为我们是按T类型注册的监听器,广播时传入的必然是T类型,所以static_cast安全
        listener(static_cast<const T&>(baseEvt));
    };

    // 直接获取对应事件的监听器列表,不存在则自动创建
    auto& listeners = s_Events[evt];
    listeners.push_back(std::move(wrappedListener));
}

std::unordered_map<GameEvent, std::vector<std::function<void(const GameEvent&)>>, GameEventHash, GameEventEqual> EventManager::s_Events;

替代实现思路(更高效可靠)

基于类型索引替代事件对象作为哈希表键,无需维护name字段,彻底避免因名称重复或对象切片导致的问题:

#pragma once

#include "events.h"

#include <functional>
#include <unordered_map>
#include <vector>
#include <type_traits>
#include <typeindex>

struct EventManager
{
    template<typename T>
    static void AddListener(std::function<void(const T&)> listener);

    template<typename T>
    static void Broadcast(const T& evt);
private:
    // 用类型索引作为键,直接关联事件类型与监听器
    static std::unordered_map<std::type_index, std::vector<std::function<void(const GameEvent&)>>> s_Events;
};

template<typename T>
void EventManager::Broadcast(const T& evt)
{
    static_assert(std::is_base_of_v<GameEvent, T>, "T must inherit from GameEvent");

    auto iter = s_Events.find(std::type_index(typeid(T)));
    if (iter != s_Events.end())
    {
        for (const auto& listener : iter->second)
        {
            listener(evt);
        }
    }
}

template<typename T>
void EventManager::AddListener(std::function<void(const T&)> listener)
{
    static_assert(std::is_base_of_v<GameEvent, T>, "T must inherit from GameEvent");

    auto wrappedListener = [listener](const GameEvent& baseEvt) {
        listener(static_cast<const T&>(baseEvt));
    };

    s_Events[std::type_index(typeid(T))].push_back(std::move(wrappedListener));
}

std::unordered_map<std::type_index, std::vector<std::function<void(const GameEvent&)>>> EventManager::s_Events;

此方案优势:

  1. 无需维护事件的name字段,减少人为错误。
  2. 类型索引的哈希与比较效率更高,避免事件对象的拷贝。
  3. 广播时直接指定事件类型,语义更清晰。

内容的提问来源于stack exchange,提问作者guyus15

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 17:05:21