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

如何将std::tuple转为std::any并用作map键实现游戏资源缓存?

问题1:是否可以将任意参数直接转换为std::any?

不行,存在两个核心限制:

  • std::any仅支持存储可拷贝构造的实值类型,不能存储引用类型、不可拷贝类型。你代码中使用std::forward_as_tuple打包的参数tuple里包含数组引用const char (&)[78]、右值引用,都不符合std::any的存储要求,因此直接触发转换错误。
  • 即使你将参数转为实值存入std::any,std::any本身没有默认的<比较运算符,无法直接作为std::map的键,这是后续会遇到的第二个编译问题。

问题2:如何将任意构造参数作为std::map的键使用?

要实现该需求,需要解决两个核心问题:一是把参数打包为支持比较的实值类型,二是对参数类型做语义适配,保证相同语义的参数(比如字符串字面量和std::string)能被识别为同一个键。

具体实现步骤

  1. 参数按值存储并做语义转换
    不要用std::forward_as_tuple(会保留引用),改用std::make_tuple存储参数的实值,同时对特殊类型做适配:比如把C风格字符串自动转为std::string,避免比较指针地址而不是字符串内容。
  2. 实现类型擦除的可比较键
    自己封装一个键类型,用类型擦除隐藏参数的具体类型,同时保留比较逻辑:
class KeyBase {
public:
    virtual ~KeyBase() = default;
    virtual bool less(const KeyBase& other) const = 0;
    virtual std::unique_ptr<KeyBase> clone() const = 0;
};

template<typename T>
class KeyImpl : public KeyBase {
public:
    explicit KeyImpl(T val) : value(std::move(val)) {}
    bool less(const KeyBase& other) const override {
        const auto* other_ptr = dynamic_cast<const KeyImpl<T>*>(&other);
        if (!other_ptr) return typeid(T).before(typeid(other));
        return value < other_ptr->value;
    }
    std::unique_ptr<KeyBase> clone() const override {
        return std::make_unique<KeyImpl<T>>(*this);
    }
private:
    T value;
};

class ErasedKey {
public:
    template<typename T>
    ErasedKey(T val) : impl_(std::make_unique<KeyImpl<T>>(std::move(val))) {}
    ErasedKey(const ErasedKey& other) : impl_(other.impl_->clone()) {}
    ErasedKey(ErasedKey&& other) noexcept = default;
    ErasedKey& operator=(const ErasedKey& other) {
        impl_ = other.impl_->clone();
        return *this;
    }
    ErasedKey& operator=(ErasedKey&& other) noexcept = default;

    bool operator<(const ErasedKey& other) const {
        return impl_->less(*other.impl_);
    }
private:
    std::unique_ptr<KeyBase> impl_;
};
  1. 修正缓存逻辑
    把map的键改成std::pair<std::type_index, ErasedKey>,同时修正对象构造的逻辑:
class SingletonFactory {
public:
    template<typename T, typename... Args>
    const T& get(Args&&... args) {
        // 对参数做decay和语义适配,C风格字符串转std::string
        auto processed_args = std::make_tuple(
            []<typename U>(U&& arg) {
                if constexpr (std::is_convertible_v<U, std::string> && !std::is_same_v<std::decay_t<U>, std::string>) {
                    return std::string(std::forward<U>(arg));
                } else {
                    return std::forward<U>(arg);
                }
            }(std::forward<Args>(args))...
        );
        auto key = std::make_pair(std::type_index(typeid(T)), ErasedKey(processed_args));

        auto it = _cache.find(key);
        if (it != _cache.end()) {
            return std::any_cast<const T&>(it->second);
        }
        // 构造新对象存入缓存
        T& obj = std::any_cast<T&>(_cache.emplace(
            std::move(key),
            T(std::forward<Args>(args)...)
        ).first->second);
        return obj;
    }

    void clear() {
        _cache.clear();
    }

private:
    std::map<std::pair<std::type_index, ErasedKey>, std::any> _cache{};
};

如果用到自定义类型作为构造参数,只要为该类型实现operator<比较运算符即可正常工作。

适配你的需求特性说明

  • 同类型同参数的资源会自动复用,避免重复加载
  • 所有资源存在工厂的map里,工厂销毁时会自动调用所有资源的析构函数,满足确定性释放的要求
  • 不需要手动追踪资源实例,调用get即可自动获取或创建资源

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 16:06:01