【问题标题】:Switch template type切换模板类型
【发布时间】:2012-01-03 10:30:37
【问题描述】:

我想为我的游戏做一些存储。现在代码看起来像:

class WorldSettings
{
    private:
        std::map<std::string, int> mIntegerStorage;
        std::map<std::string, float> mFloatStorage;
        std::map<std::string, std::string> mStringStorage;

    public:
        template <typename T>
        T Get(const std::string &key) const
        {
            // [?]
        }
};

所以,我有一些关联容器来存储确切类型的数据。现在我想在设置中添加一些值:settings.Push&lt;int&gt;("WorldSize", 1000); 并得到它:settings.Get&lt;int&gt;("WorldSize");。但是由于传递的类型到模板中,如何切换需求映射?

或者,也许,您知道更好的方法,谢谢。

【问题讨论】:

    标签: c++ templates types map


    【解决方案1】:

    首先,由于在 C++11 之前你不能特化函数,你的成员函数必须在签名上有所不同——返回类型不计算在内。根据我在某些编译器上的经验,你可以不用它,但像往常一样 - 你应该让你的代码尽可能接近标准。

    也就是说,您可以添加一个不会影响性能和调用函数方式的虚拟参数:

    public:
        template <typename T>
        T Get(const std::string &key) const
        {
            return GetInner(key, (T*)0);
        }
    
    private:
        int GetInner(const std::string& key, int*) const
        {
            // return something from mIntegerStorage
        }
    
        float GetInner(const std::string& key, float*) const
        {
            // return something from mFloatStorage
        }
    

    等等。你明白了。

    【讨论】:

    • 是的,非常有趣的想法。会记住这一点的。
    • 这是一种无需模板专业化的非常好的方法。出于多种原因,我建议使用引用而不是指针,但仍然是 +1
    • 我使用指针是因为你总是可以给它们赋值 0。您将如何使用引用?
    • 你不需要在接口上添加虚拟指针,它可以只转发到'GetInner(key,(T*)0)',即额外的指针是一个不应该的细节修改公共接口。
    • 与其提供不同的GetInner 函数,不如提供map&lt;string,X&gt; const &amp; GetStorage( X* ) const 重载来返回对适当容器的引用,这样您就可以删除冗余代码(在容器中查找,错误情况...不需要在每个GetInner 函数中重复,而是可以在Get 模板中处理)
    【解决方案2】:

    如果你的编译器支持这个1,你可以使用模板函数特化:

    class WorldSettings
    {
        private:
            std::map<std::string, int> mIntegerStorage;
            std::map<std::string, float> mFloatStorage;
            std::map<std::string, std::string> mStringStorage;
    
        public:
            template <typename T>
            T Get(const std::string &key); // purposely left undefined
    };
    
    ...
    
    template<>
    int WorldSettings::Get<int>(const std::string& key) {
        return mIntegerStorage[key];
    }
    
    template<>
    float WorldSettings::Get<float>(const std::string& key) {
        return mFloatStorage[key];
    }
    
    // etc
    

    请注意,这些方法不是const,因为map&lt;&gt;::operator[] 不是const

    此外,如果有人试图将模板与您提供的专业化类型不同的类型一起使用,他们将收到链接器错误,因此您的代码不会出现任何异常或任何问题。哪个是最优的。


    1 如果没有,请参阅@gwiazdorrr's answer

    【讨论】:

    • @Ockonal 是的。模板特化是 C++11 引入的最有用的特性之一。
    • 虽然模板函数特化很简洁,但它们不是 C++11 特定的。由于它们与正常函数重载的交互方式,它们并不适用于所有情况。这是它们适用的情况之一。见gotw.ca/gotw/049.htm
    • @DaveS 如果没有在 C++11 中引入模板函数特化,那是什么时候引入的?我不认为 C++03 让他们做到了?
    • @Seth Section 14.7.3 An explicit specialization of any of the following: function template ...,然后有一个例子template&lt;class T&gt; void sort(Array&lt;T&gt;&amp; v) { /* ... */ } template&lt;&gt; void sort&lt;char*&gt;(Array&lt;char*&gt;&amp;) ;
    • @DaveS 哇,我从来不知道,这很酷。谢谢。为什么编译器支持对此不好? (或者我只是想象它很糟糕?)
    【解决方案3】:

    在 C++03 中,我建议在容器类型中使用“boost::any”,并且.您需要一个访问器:

    std::map<std::string,boost::any> storage;
    template <typename T> getValue( std::string const & key ) {
       return boost::any_cast<T>( storage[key] );
    }
    

    这是一个粗略的草图,作为一个成员函数它应该是const,它应该使用'map::find'在搜索时不要修改容器,它应该处理无效的joey,并且可能将boost异常重新映射到您自己的应用程序例外。

    【讨论】:

    • 这不会增加内存消耗和碎片,(boost::any 分配一个内部对象 IIRC)和计算成本(内部虚函数,比较 type_id)?我知道这可能不是游戏代码的关键部分,但作为政策的一部分,您应该避免这些事情。
    • @gwiazdorrr:对于设置和配置,您应该使用更容易维护的任何东西。性能被许多人高估了,但你需要知道它在哪里重要,在哪里不重要。你是对的,这个解决方案使用更多内存,涉及动态分配和一些 RTTI。如果您需要性能,请完全忘记映射并使用具有所需方法的手工类,但在大多数情况下,如果映射中的动态分配不是问题,那么使用 boost::any 也不应该。
    • 我不同意你的结论。地图插入速度很慢(节点分配),但查找速度相当快。因此,只要您不经常插入或删除节点(例如,您只在初始化阶段执行此操作),这是一个不错的选择。 boost::any 无论如何都很慢,虽然我同意它有它的用途,但对于给定的问题,它似乎是一个臃肿的解决方案。
    【解决方案4】:

    Seth 的答案是理想的,但如果您无法访问 C++11 编译器,那么您可以使用模板类专业化来代替。它更冗长,但保持相同的功能。

    class WorldSettings
    {
        template<class T>
        struct Selector;
        template<class T>
        friend struct Selector;
    
    private:
        std::map<std::string, int> mIntegerStorage;
        std::map<std::string, float> mFloatStorage;
        std::map<std::string, std::string> mStringStorage;
    
    public:
        template <typename T>
        T Get(const std::string &key)
        {
            return Selector<T>::Get(*this)[key];
        }
    };
    
    template<>
    struct WorldSettings::Selector<int>
    {
        static std::map<std::string, int> & Get(WorldSettings &settings)
        {
            return settings.mIntegerStorage;
        }
    };
    
    template<>
    struct WorldSettings::Selector<float>
    {
        static std::map<std::string, float> & Get(WorldSettings &settings)
        {
            return settings.mFloatStorage;
        }
    };
    
    // etc.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-30
      • 2016-05-13
      • 1970-01-01
      • 2015-06-27
      • 1970-01-01
      • 1970-01-01
      • 2021-06-22
      相关资源
      最近更新 更多