2015-08-13 11 views
6

Sto solo provando qualcosa - Volevo creare un factory generico che restituisca shared_ptr a un tipo.C++ Factory che utilizza i problemi dei modelli variadic

Ho una classe derivata che utilizza metodi statici per restituire un shared_ptr alla classe base. L'idea è che mi piacerebbe poter registrare questi metodi con un factory generico, ma non è in grado di determinare quale metodo registrare al momento della compilazione. Forse c'è un modo per ottenere questo usando SFINAE, ma sto appena cominciando a capire le sue complessità.

Ci scusiamo per l'esempio di codice piuttosto lungo, disponibili anche presso http://coliru.stacked-crooked.com/a/331e08de86004592

attivazione di più di uno dei metodi di fabbrica in 'DerivedA' causeranno un errore di compilazione.

#include <iostream> 
#include <string> 
#include <vector> 
#include <unordered_map> 
#include <memory> 

// Factory which returns a shared_ptr of type T. 
template<class T, class Tag, class... Args> 
class NameFactory 
{ 
public: 
    typedef std::function<std::shared_ptr<T>(Args...)> Function; 

    static NameFactory& instance(); 

    void registerType(const std::string& type, const Function& createFunction); 
    std::shared_ptr<T> createObject(const std::string& type, Args&&... arguments); 
private: 
    NameFactory() {} 

    std::unordered_map<std::string, Function> m_functionMap; 
}; 

template<class T, class Tag, class... Args> 
NameFactory<T, Tag, Args...>& NameFactory<T, Tag, Args...>::instance() 
{ 
    static NameFactory<T, Tag, Args...> m_instance; 
    return m_instance; 
} 

template<class T, class Tag, class... Args> 
void NameFactory<T, Tag, Args...>::registerType(const std::string& type, const Function& createFunction) 
{ 
    m_functionMap[type] = createFunction; 
} 

template<class T, class Tag, class... Args> 
std::shared_ptr<T> NameFactory<T, Tag, Args...>::createObject(const std::string& type, Args&&... arguments) 
{ 
    auto iter(m_functionMap.find(type)); 

    if (iter != m_functionMap.end()) 
    { 
     return (iter->second)(std::forward<Args>(arguments)...); 
    } 

    throw std::logic_error("Cannot find constructor for type '" + type + "'"); 
} 

template<class T, class Tag, class... Args> 
class NameFactoryRegistration 
{ 
public: 
    typedef NameFactory<T, Tag, Args...> Factory; 
    NameFactoryRegistration(const std::string& type, const typename Factory::Function& createFunction) 
    { 
     Factory::instance().registerType(type, createFunction); 
    } 
private: 
}; 

class MyBase 
{ 
public: 
    typedef std::shared_ptr<MyBase> SPtr; 
}; 

class DerivedA : public MyBase 
{ 
public: 
    static SPtr create() 
    { 
     return SPtr(new DerivedA); 
    } 

    // Enabling this factory method (and/or the two args method below causes an 'unresolved overloaded function type' error 
    //static SPtr create(const std::string& s) 
    //{ 
    // return SPtr(new DerivedA(s)); 
    //} 

    //static SPtr create(const std::string& s, double d) 
    //{ 
    // return SPtr(new DerivedA(s,d)); 
    //} 
private: 
    DerivedA() 
    { 
     std::cout << "DerivedA - no args" << std::endl; 
    } 

    DerivedA(const std::string& s) 
    { 
     std::cout << "DerivedA - one arg: " << s << std::endl; 
    } 

    DerivedA(const std::string& s, double d) 
    { 
     std::cout << "DerivedA - two args: " << s << " : " << d << std::endl; 
    } 
}; 

// Tags to help differentiate the factories 
struct NoArgsReg; 
struct SingleArgReg; 
struct TwoArgReg; 

typedef NameFactory<MyBase, NoArgsReg> NoArgsFactory; 
typedef NameFactoryRegistration<MyBase, NoArgsReg> NoArgsRegistration; 

typedef NameFactory<MyBase, SingleArgReg, const std::string&> SingleArgFactory; 
typedef NameFactoryRegistration<MyBase, SingleArgReg, const std::string&> SingleArgRegistration; 

typedef NameFactory<MyBase, TwoArgReg, const std::string&, double> TwoArgsFactory; 
typedef NameFactoryRegistration<MyBase, TwoArgReg, const std::string&, double> TwoArgsRegistration; 

// Register the factory methods into the NameFactory 
NoArgsRegistration dAReg0("A", DerivedA::create); 
//SingleArgRegistration dAReg1("A", DerivedA::create); 
//TwoArgsRegistration dAReg2("A", DerivedA::create); 


int main() 
{ 
    auto object0(NoArgsFactory::instance().createObject("A")); 

    // Not registered, 
    //auto object1(SingleArgFactory::instance().createObject("A","testString")); 
    //auto object2(TwoArgsFactory::instance().createObject("A","testString",3.142)); 

    return 0; 
} 

risposta

2

Il problema è che non è possibile dedurre il tipo in un set di sovraccarico. Anche se semplifichiamo l'esempio a qualcosa che potremmo provare ad usare SFINAE con, siamo bloccati:

#include <functional> 

struct A { 
    static void create() { } 
    static void create(int) { } 
}; 

template <typename F, 
      typename = decltype(std::declval<F>()(std::declval<int>()))> 
void foo(F) { } 

int main() { 
    foo(&A::create); // error, even in this case 
} 

dovreste aggiungere sovraccarichi esplicite per puntatori a funzione per gestire questo caso, in quanto v'è un un'eccezione nella norma per consentire che:

void foo(void (*)(int)) { } // (1) 

template <typename F, 
      typename = decltype(std::declval<F>()(std::declval<int>()))> 
void foo(F) { }   // (2) 

int main() { 
    foo(&A::create); // OK, calls (1) 
} 

nel tuo esempio specifico, questo significa che l'aggiunta di due costruttori:

// in Factory 
using Function = std::function<std::shared_ptr<T>(Args...)>; 
using FunctionPtr = std::shared_ptr<T>(*)(Args...); 

// in Registration 
using Function = typename Factory::Function; 
using FunctionPtr = typename Factory::FunctionPtr; 

NameFactoryRegistration(const std::string& type, const Function& createFunction) { 
    /* same as before */ 
} 

NameFactoryRegistration(const std::string& type, FunctionPtr createFunction) 
: NameFactoryRegistration(type, Function(createFunction)) 
{ } 
+0

Fantastico, grazie Barry - molto utile sapere! Non avevo pensato di usare i puntatori di funzione, avevo considerato std :: function come un "magic bullet" che poteva sostituirli completamente. – DaveM

3

Il problema è che (prima di C++ 14) std::function<R(A...)> può essere costruito da nulla, non solo da qualcosa che supporta un R(A...) chiamata. Dovrebbe essere d'aiuto se si aggiunge un sovraccarico di registerType che avrà un parametro .

+0

non credo che l'aggiunta di SFINAE per 'std :: function' aiuterebbe. – Barry

+0

Grazie Angew - come ho detto di seguito, non avevo considerato come il compilatore avrebbe interpretato i puntatori di funzioni vs std :: function, e pensato che std :: function avrebbe funzionato come una sostituzione drop-in. – DaveM