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

C++ ECS实现中返回派生类型shared_ptr向量引用问题

问题根源

你当前的组件存储用std::type_index做键,统一存储std::vector<std::shared_ptr<IEComponent>>类型的组件列表,存在两个天然的类型适配问题:

  • C++中std::vector<std::shared_ptr<派生类>>和std::vector<std::shared_ptr<基类>>是完全独立的类型,不存在隐式转换,无法直接把基类vector当派生类vector使用
  • 如果逐元素做指针转换后拷贝生成新的派生类vector,操作的是独立的vector副本,对新vector做的增删、元素替换操作不会同步回原存储;但实际上你通过副本里的shared_ptr修改组件实例本身的成员是会生效的,只是vector层面的操作不同步,且每次调用都拷贝vector会产生无意义的性能开销。
实现方案

不需要修改现有存储结构,通过轻量的视图包装类实现零拷贝的类型适配,遍历访问时实时做静态指针转换即可——由于组件存入容器时已经通过typeid(T)严格匹配类型,不需要运行时动态类型检查,无额外开销。

首先补全你现有代码中已经调用的基类vector获取接口:

std::vector<std::shared_ptr<IEComponent>>& GetBaseComponents(std::type_index type_idx) {
    return components_map.at(type_idx);
}

实现组件视图包装类,绑定原存储vector的引用,适配范围for遍历的迭代器接口:

// 约束:所有传入的组件类型T必须公开继承自IEComponent
template<typename T>
class ComponentView {
private:
    std::vector<std::shared_ptr<IEComponent>>& target_vec;
public:
    ComponentView(std::vector<std::shared_ptr<IEComponent>>& vec) : target_vec(vec) {}

    // 范围for所需的迭代器实现
    class Iterator {
    private:
        std::vector<std::shared_ptr<IEComponent>>::iterator base_iter;
    public:
        Iterator(decltype(base_iter) it) : base_iter(it) {}

        // 解引用时直接做静态指针转换,无运行时检查开销
        std::shared_ptr<T> operator*() {
            return std::static_pointer_cast<T>(*base_iter);
        }

        Iterator& operator++() {
            base_iter++;
            return *this;
        }

        bool operator!=(const Iterator& other) const {
            return base_iter != other.base_iter;
        }
    };

    Iterator begin() { return Iterator(target_vec.begin()); }
    Iterator end() { return Iterator(target_vec.end()); }

    // 补充常用vector操作接口,直接透传到原存储
    size_t size() const { return target_vec.size(); }
    std::shared_ptr<T> operator[](size_t idx) {
        return std::static_pointer_cast<T>(target_vec[idx]);
    }
    void Erase(size_t idx) {
        if (idx < target_vec.size()) {
            target_vec.erase(target_vec.begin() + idx);
        }
    }
};

最后实现你需要的GetComponents<T>()模板接口:

template<typename T>
ComponentView<T> GetComponents() {
    std::type_index type_idx(typeid(T));
    // 不存在对应类型组件列表时自动创建空vector,和CreateComponent逻辑对齐
    if (components_map.find(type_idx) == components_map.end()) {
        components_map.emplace(type_idx, std::vector<std::shared_ptr<IEComponent>>());
    }
    return ComponentView<T>(GetBaseComponents(type_idx));
}
使用效果

完全匹配你预期的调用写法,不需要修改上层业务代码:

for (std::shared_ptr<ECPosition> pos : ComponentManager::GetComponents<ECPosition>()) {
    TraceLog(LOG_INFO, "x is: %f y is: %f", pos->x, pos->y);
    // 直接修改组件成员、通过视图调用Erase删除组件等操作,都会直接作用在原存储上,改动永久生效
    pos->x += 1.0f;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 05:24:16