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

C++如何从函数指针推导资源类型,生成GetResource调用实参列表?

问题:自动推导系统函数的资源依赖并传递参数

我正在开发一套系统,允许用户通过定义数据片段(称为resources)和操作这些资源的函数(称为systems)来创建应用。希望用户仅提供特定类型的函数指针,从中推导需要传递给该函数的资源类型。

上下文代码

#include <vector>
#include <unordered_map>
#include <memory>

using TypeID = size_t;

class Type
{
public:
    template <class T>
    static TypeID ID()
    {
        static TypeID id = s_Counter++;

        return id;
    }

private:
    inline static TypeID s_Counter = 0;
};

struct Resource
{
   virtual ~Resource() = default;
};

template <typename T>
struct Res : public Resource
{
    inline static const TypeID ID = Type::ID<T>(); // Type::ID<T>() 为每个类型返回唯一ID
    T Data;
};

class Application
{
private:
    std::unordered_map<TypeID, std::unique_ptr<Resource>> m_Resources;
    std::vector<void(*)()> m_Systems;

public:
    template <typename T>
    void AddResource()
    {
        m_Resources[Res<T>::ID] = std::make_unique<Res<T>>();
    }

    template <typename T>
    T& GetResource()
    {
        return m_Resources[Res<T>::ID]->Data;
    }

    template <typename... Resources>
    void AddSystem(void (*pSystem)(Resources...))
    {
        m_Systems.push_back([pSystem]() {
            pSystem(/* 需要将参数包Resources展开为GetResource<每个类型>()的调用列表 */);
        });
    }
};

struct Foo
{
    int a;
    float b;
};

void system(Foo foo, int num, float val)
{
    /* 使用foo、num、val进行操作 */
}

int main()
{
    Application app;

    app.AddResource<Foo>();
    app.AddResource<int>();
    app.AddResource<float>();

    app.AddSystem(system);
}

核心需求:在AddSystem函数中,将模板参数包(例如<Foo, int, float>)展开为GetResource<Foo>(), GetResource<int>(), GetResource<float>(),并将这些调用的返回值作为实参传递给用户定义的pSystem函数,最终生成代码pSystem(GetResource<Foo>(), GetResource<int>(), GetResource<float>());。


可移植的实现方案

1. 直接展开参数包(C++11及以上,最简洁)

函数调用中的参数包展开是C++11引入的标准特性,完全可移植。只需修改AddSystem中的lambda,捕获this(因为GetResource是成员函数)并直接展开参数包:

template <typename... Resources>
void AddSystem(void (*pSystem)(Resources...))
{
    m_Systems.push_back([this, pSystem]() {
        pSystem(GetResource<Resources>()...);
    });
}

关键说明:

  • GetResource<Resources>()...会将参数包Resources中的每个类型R替换为GetResource<R>(),自动生成对应的实参列表。
  • 必须捕获this,否则无法调用成员函数GetResource。

2. C++11兼容的冗余展开(备选)

如果需要兼容更早期的编译器(虽然C++11已经普及),可以用初始化列表辅助展开:

template <typename... Resources>
void AddSystem(void (*pSystem)(Resources...))
{
    m_Systems.push_back([this, pSystem]() {
        // 使用初始化列表强制展开参数包
        auto call = [&](){ pSystem(GetResource<Resources>()...); };
        call();
    });
}

其实本质和第一种方案一致,只是显式封装了调用,确保参数包正确展开。


额外的设计优化建议

  • 传递资源引用而非值:当前系统函数按值传递资源会触发拷贝,建议修改为接受引用,GetResource返回的引用可直接传递,无需修改AddSystem实现:

    void system(Foo& foo, int& num, float& val) { /* ... */ }
    
  • 资源存在性检查:原代码中GetResource使用m_Resources[Res<T>::ID]会在资源不存在时创建空指针,导致未定义行为,建议添加检查:

    template <typename T>
    T& GetResource()
    {
        auto it = m_Resources.find(Res<T>::ID);
        if (it == m_Resources.end()) {
            throw std::runtime_error("Resource not found");
        }
        return static_cast<Res<T>*>(it->second.get())->Data;
    }
    
  • 支持资源初始化:允许用户添加资源时传入初始值,提升易用性:

    template <typename T, typename... Args>
    void AddResource(Args&&... args)
    {
        m_Resources[Res<T>::ID] = std::make_unique<Res<T>>(std::forward<Args>(args)...);
    }
    

    使用示例:app.AddResource<Foo>({1, 3.14f});

  • 添加const版本的GetResource:支持在const上下文中安全获取资源:

    template <typename T>
    const T& GetResource() const
    {
        auto it = m_Resources.find(Res<T>::ID);
        if (it == m_Resources.end()) {
            throw std::runtime_error("Resource not found");
        }
        return static_cast<const Res<T>*>(it->second.get())->Data;
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 10:35:57