c++ - Extensible architecture using Abstract Factory and template metaprogramming -


i'm working on master thesis , seems cannot find satisfying solution following problem. idea here design small library should abstract underlying apis (such directx11 , opengl4). not required 2 or more apis coexist in same application, theoretically write bunch of preprocessor directives discriminate among them, blow code and, of course, not extensible @ all.

the abstract factory seems handy, seems cannot find way make work templates.

let's start...

i have abstract class factory purpose instantiate objects needed application work, such resources , context. former used load resources @ runtime, while latter used render 3d scene. both resources , context abstract because implementation depends on underlying api.

class factory{    public:       virtual resources & getresources() = 0;       virtual context & getcontext() = 0; } 

resources class load resources needed, , return objects of type texture2d , mesh. again, classes abstract since depend on specific api.

let's i'm working directx11 , opengl4.5. each of apis have classes above derived, respectively dx11factory, dx11resources, dx11context, dx11texture2d, dx11mesh , on. class extend pretty obvious. fair enough.

the trivial way design interface of class resource following:

class resources{    public:       texture2d loadtexture(const wstring & path) = 0;       mesh loadmesh(const wstring & path) = 0; } 

the class dx11resource implement methods above , work fine... except if wanted support new resource type in future texturecube (and software engineer point of view, sure. right i don't care), i'll have declare new method texturecube loadtexturecube(...) in interface library user use, resources. mean i`ll have implement method in every single derived class (open-closed principle ftw!).

my first idea solve problem following:

class texture2d{...}  class resources{    public:       template<typename tresource>       virtual tresource load(const wstring & path) = 0; // :( }     namespace dx11{     class dx11texture2d: public texture2d{...}    class dx11texture2dloader{...}     template<typename tresource> struct resource_traits;     template<> struct resource_traits<texture2d>{        using type = dx11texture2d;       using loader = dx11texture2dloader; //functor type     }     class dx11resources{       public:          template<typename tresource>          virtual tresource load(const wstring & path){              return typename resource_traits<tresource>::loader()( path );           }    }  } 

so if need support new type of resource declare new resource_traits inside proper namespace (and of course new resource abstract , concrete type) , work. unfortunately virtual template methods not supported (and reason, imagine happen writing

resources * r = grabresources(); //it return directx9 object r->load<hullshader>(l"blah");  //dx9 doesn't support hullshaders, have no resource_traits<hullshader> 

so compiler won't able perform proper substitution , point out error class user wasn`t aware of. )

i have thought other solutions none of them satisfy needs:

1. crtp

i can use this:

template <typename tderived> class resources{    public:        template <typename tresource>       tresource load(const wstring & path){           return typename tderived::resource_traits<tresource>::loader()( path );        } } 

i think work, resources<tderived> cannot returned factory object because tderived not known (and final programmer shouldn`t anyway).

2. rtti

class resources{    template <typename tresource>    tresource load(const wstring & path){        return *static_cast<tresource *>( load(path, typeid(tresource).hash_code()) );     }     virtual void * load(const wstring & path, size_t hash) = 0; } 

in derived class have implement pure virtual method above , then, using if-then-else cascade can instantiate resource need or return nullptr if particular api doesn't support it. work sure ugly , of course forces me rewrite implementation whenever want support new resource type (but @ least 1 class)!

if( hash == typeid(texture2d).hash_code()) // instantiate dx11texture2d else if (...)... 

3. visitor

taking advantage of visitor pattern. method acttually won't me @ all, leave here in case (i think visitor whenever see never-ending if-then-else cascade integrated downcast, in previous point :) ).

template <typename tresource> resource_traits;  template<> resource_traits<texture2d>{     using visitable = texture2dvisitable;  }  struct texture2dvisitable{     texture2d operator()(const wstring & path, loader & visitor){        return visitor.load(path, *this);     }  }  template<typename tresource> tresource resources::load(path){     return typename resource_traits<tresource>::visitable()(path, *this);  } 

using approach resources have declare pure virtual method every resource can load texture2d resources::load(path, texture2dvisitable &) = 0. so, again, in case of new resources have update entire hierarchy accordingly... @ point use trivial solution @ beginning.

4. others?

have missed something? approach should prefer? feel i'm overcomplicating stuffs, always!

thanks in advance , sorry poorly-written wall-o-text!

ps: rid of resource class in first place not option since real purpose prevent loading of same resource on , over. huge flyweight.

this problem boils down whole "virtual function template" problem. basically, solution (whatever is) has take compile-time information (e.g., template argument), turn run-time information (e.g., value, type-id, hash-code, function-pointer, etc.), go past run-time dispatch (virtual call), , turn run-time information compile-time information (e.g., piece of code execute). understanding this, you'll realize direct solution use "rtti" solution, or variation thereof.

as point out, real problem solution is "ugly". agree bit ugly , other that, it's nice solution, fact modifications needed when adding new supported type localized implementation (cpp files) associated class adding support (you not hope better that).

as ugliness, well, that's can improve on trickery, there ugliness it, static_cast cannot removed because need way emerge run-time dispatch statically typed result. here possible solution relies on std::type_index:

// resources.h:  class resources {   public:     template <typename tresource>     tresource load(const wstring & path){       return *static_cast<tresource *>(load(path, std::type_index(typeid(tresource))));     }    protected:     virtual void* load(const wstring & path, std::type_index t_id) = 0; }  // dx11resources.h:  class dx11resources : public resources {   protected:     void* load(const wstring & path, std::type_index t_id); };  // dx11resources.cpp:  template <typename tresource> void* dx11res_load(dx11resources& res, const wstring & path) { };  template <> void* dx11res_load<texture2d>(dx11resources& res, const wstring & path) {   // code load texture2d };  // .. on other things..  void* dx11resources::load(const wstring & path, std::type_index t_id) {   typedef void* (*p_load_func)(dx11resources&, const wstring&);   typedef std::unordered_map<std::type_index, p_load_func> maptype;    #define dx11res_support_loader(typename) maptype::value_type(std::type_index(typeid(typename)), dx11res_load<typename>)    static maptype func_map = {     dx11res_support_loader(texture2d),     dx11res_support_loader(texture3d),     dx11res_support_loader(texturecube),     //...   };    #undef dx11res_support_loader    auto = func_map.find(t_id);   if(it == func_map.end())     return nullptr;  // or throw exception, whatever prefer.    return it->second(*this, path); }; 

there variations (such having member functions instead of free functions loaders, or using non-template functions instead of specialization, or both of these modifications), basic idea add new supported type, add list of supported types (the dx11res_support_loader(sometype)) list , create code new function (only in cpp file). there still bit of ugliness in there, header file clean, , ugliness in virtual "load" "o(1)" in complexity, meaning don't add ugliness every new type, it's constant bit of ugly code (instead of if-else sequence, amount of ugly code proportional number of types supported). also, has side benefit of being faster in doing dispatching (with hash table). also, using type_index important avoid collisions hash values of 2 types (you don't lose info on typeid used create hash value).

so, in all, recommendation go "rtti" solution, , can or want remove of ugliness or inefficiencies associated it. important thing keep interface (header, class declaration) of derived class clean possible avoid having add in future (you don't want class expose, in declaration, types of resources supports via function declarations or something, otherwise, have recompile world every time add one).

n.b.: if need avoid using rtti (e.g., -fno-rtti option), there ways work around problem, it's out of scope of question.


Comments

Popular posts from this blog

javascript - Jquery show_hide, what to add in order to make the page scroll to the bottom of the hidden field once button is clicked -

javascript - Highcharts multi-color line -

javascript - Enter key does not work in search box -