创建泛型转换函数

Creating a generic conversion function

本文关键字:函数 转换 泛型 创建      更新时间:2023-10-16

我有一个ResourceManager,它接受类型为Resource的类。ResourceShaderProgramTextureMesh甚至Camera等完全不相关的其他类的父类。

可以这么说,ResourceManager是有效的。但是有一件事是非常乏味和烦人的,那就是当我从ResourceManager检索对象时。问题在这里:

为了从ResourceManager获得一个对象,你调用以下两个函数之一:

static Resource* get(int id);
static Resource* get(const std::string &name);

第一个函数通过一个整数id检查一个std::unordered_map;而第二个函数通过客户端手动给出的名称检查另一个std::unordered_map。出于灵活性考虑,我有两个版本的这些函数,因为有时我们不关心ResourceManager中包含的对象是什么(如Mesh),有时我们关心它是什么(如CameraShaderProgram),因为我们可能希望通过名称而不是id检索所述对象。

无论哪种方式,两个函数都返回指向Resource的指针。当您调用该函数时,就像这样简单:

rm::get("skyboxShader");

其中rm只是ResourceManagertypedef,因为该类是静态的(所有成员/函数都是静态的)。问题是,rm::get(..)函数返回的是Resource*,而不是一开始添加到ResourceManager的子类。所以,为了解决这个问题,我必须做一个手动类型转换,这样我就可以得到ShaderProgram*而不是Resource*。我是这样做的:

auto s = static_cast<ShaderProgram*>(rm::get(name));

所以,每次我想访问Resource时,我必须插入我想要实际进入static_cast的类型。这是有问题的,因为每次有人需要访问Resource时,他们必须键入转换它。所以,我自然地创建了一个函数,并且ShaderProgram是这里的主题,因此:

ShaderProgram* Renderer::program(const std::string &name)
{
    auto s = static_cast<ShaderProgram*>(rm::get(name));
    return s;
}

这个函数是静态的,而ResourceManager是一个静态类,所以两者可以很好地结合在一起。这是一个很好的辅助函数,它有效地工作,我的程序渲染结果很好。问题是当我处理其他Resource时,我必须做什么;这意味着对于每个存在的Resource,都必须有一个类型转换函数来容纳它。这很烦人。有没有一种方法可以写一个类似这样的泛型类型转换函数?

auto Renderer::getResource(classTypeYouWant T, const std::string &name)
{
    auto s = static_cast<T*>(rm::get(name));
    return s;
}

在这里,auto关键字使函数派生出它应该处理的类型并相应地返回结果。我的第一个猜测是,我可能不得不使用模板;但模板的问题是,我不能限制哪些类型插入到函数中,我真的真的不想要浮点id数,char id,更不用说自定义的id。它要么是字符串(可能会更改为const char* tbh),要么是整型,或者其他类型。

如何创建像上面描述的那样的通用转换函数?

您看过使用dynamic_cast吗?如果使用dynamic_cast转换失败,指针将被设置为nullptr。因此,您可以为每种类型编写重载,也可以编写模板函数,其中传递要转换为的类型以及字符串或id,如果转换成功或失败,则返回truefalse

template<typename T>
bool Renderer::getResource(T*& type, const std::string &name)
{
    type = dynamic_cast<decltype(std::remove_reference<decltype(T)>::type)>(rm::get(name));
    if (type == nullptr)
        return false;
    return true;
}

好吧,我不喜欢无类型存储的想法,但也许您可以将基本程序作为起点。有很多东西必须美化,但有些作品必须保留:-)

再次强调:用这种方式解决问题是设计上的失败!

除了您的示例代码之外,此解决方案在检查存储类型时提供了最低限度的安全性,同时召回元素。但是这个解决方案需要rti,并且不是在所有平台上都可用。

#include <map>
#include <iostream>
#include <typeinfo>
class ResourcePointerStorage
{
    private:
        std::map< const std::string, std::pair<void*, const std::type_info*>> storage;
    public:
        bool Get(const std::string& id, std::pair<void*, const std::type_info*>& ptr )
        {
            auto it= storage.find( id );
            if ( it==storage.end() ) return false;
            ptr= it->second;
            return true;
        }
        bool Put( const std::string& id, void* ptr, const std::type_info* ti)
        {
            storage[id]=make_pair(ptr, ti);
        }
};
template < typename T>
bool Get(ResourcePointerStorage& rm, const std::string& id, T** ptr) 
{
    std::pair<void*, const std::type_info*> p;
    if ( rm.Get( id,p ))
    {
        if ( *p.second != typeid(T)) { return false; }
        *ptr= static_cast<T*>(p.first);
        return true;
    }
    else
    {
        return 0;
    }
}
template < typename T>
void Put( ResourcePointerStorage& rm, const std::string& id, T *ptr)
{
    rm.Put( id, ptr, &typeid(T) ); 
}
class Car
{
    private:
        int i;
    public:
        Car(int _i):i(_i){}
        void Print() { std::cout << "A car " << i << std::endl; }
};
class Animal
{
    private:
        double d;
    public:
        Animal( double _d):d(_d) {}
        void Show() { std::cout << "An animal " << d << std::endl; }
};
int main()
{
    ResourcePointerStorage store;
    Put( store, "A1", new Animal(1.1) );
    Put( store, "A2", new Animal(2.2) );
    Put( store, "C1", new Car(3) );
    Animal *an;
    Car *car;
    if ( Get(store, "A1", &an)) { an->Show(); } else { std::cout << "Error" << std::endl; }
    if ( Get(store, "A2", &an)) { an->Show(); } else { std::cout << "Error" << std::endl; }
    if ( Get(store, "C1", &car)) { car->Print(); } else { std::cout << "Error" << std::endl; }
    // not stored object
    if ( Get(store, "XX", &an)) { } else { std::cout << "Expected false condition" << std::endl; }
    // false type
    if ( Get(store, "A1", &car)) { } else { std::cout << "Expected false condition" << std::endl; }
};

我已经找到解决问题的方法了。我创建了一个宏:

#define convert(type, func) dynamic_cast<type>(func)

非常泛型和代码中立,允许从函数的返回类型动态转换类型。它还允许执行检查:

if (!convert(ShaderProgram*, rm::get("skyboxShader")))
    cerr << "Conversion unsuccessful!" << endl;
else cout << "Conversion successful!" << endl;

我希望我的解决方案能帮助那些搜索类似问题的人。感谢所有!