2017-01-03 1 views
1

J'ai deux classes abstraites C++ Abs1 et Abs2. Puis-je:Création d'objet générique à partir de la ligne de commande

`A : public Abs1` 
`B : public Abs1` 
`C : public Abs2` 
`D : public Abs2` 

Maintenant, je suis en train de create objects from command line arguments et je dois faire réécrire la fonction d'usine publique make_abstract dans la question liée, quelque chose comme:

std::unique_ptr<Abs1> makeAbs1 (int argc, const char*argv[]) 
{ 

    if (argc == 1) { 
     return nullptr; 
    } 
    const std::string name = argv[1]; 
    if (name == "A") { 
     return detail::make_abstract<A, std::tuple<int, std::string, int>>(argc, argv); 
    } else if (name == "B") { 
     return detail::make_abstract<B, std::tuple<int, int>>(argc, argv); 
    } 
} 



std::unique_ptr<Abs2> makeAbs2 (int argc, const char*argv[]) 
{ 

    if (argc == 1) { 
     return nullptr; 
    } 
    const std::string name = argv[1]; 
    if (name == "C") { 
     return detail::make_abstract<C, std::tuple<int>>(argc, argv); 
    } else if (name == "D") { 
     return detail::make_abstract<D, std::tuple<int, float>>(argc, argv); 
    } 
} 

Comme vous pouvez le voir est terriblement redondant. Comment puis-je faire une version générique de ceci? Dans cette version, nous pouvons passer autant de classes implémentées que nous le voulons, donc la cascade if n'est pas une solution. Notez que nous ne pouvons pas modifier l'une de ces classes.

Je pensais que peut-être modèles variadique pourrait aider, mais je ne peux pas comprendre de nombreux problèmes:

template <typename T, typename ...Ts> 
std::unique_ptr<T> make (int argc, const char*argv[]){ 
    const std::string name = argv[1]; 
    for(Ti : Ts) //this is obviously wrong 
    if(typeid(Ti).name == name) 
     return detail::make_abstract<T, std::tuple</*Here shoudl be different for every Ti*/>>(argc, argv); 

} 
+0

Vous ne pouvez pas entasser les types non apparentés dans un type de retour. À quoi ressemblerait-il sur le site d'appel? Soit il doit y avoir une classe de base partagée, ou une union (ou std :: any, etc) pour rendre utilisable – Caleth

Répondre

2

Ooooh, c'était amusant :)

[TL; DR: il y a un exemple en direct à la fin]

J'ai implémenté deux couches de mappage sur votre (vos) fonction (s) detail::make_abstract. Commençons par le code d'appel:

int main(int argc, char **argv) { 
    std::unique_ptr<Abs1> p1; 
    std::unique_ptr<Abs2> p2; 

    makeEverything(argc, argv, p1, p2); 
} 

Ici, nous appelons makeEverything avec argc, argv, et une liste de std::unique_ptr s. Une fois la fonction terminée, l'un des pointeurs contiendra un objet du type correct.

Allons plus loin.

inline void makeEverything(int, char**) { } 

template <class Abs, class... Abses> 
void makeEverything(int argc, char **argv, 
    std::unique_ptr<Abs> &abs, std::unique_ptr<Abses> &... abses) { 

    abs = makeAbs<Abs>(argc, argv); 

    if(!abs) 
     makeEverything(argc, argv, abses...); 
} 

Ceci est votre modèle de fonction variadique habituelle récursive: prendre le premier pointeur, essayez de construire un objet pour elle. S'il échoue, jetez-le et réessayez avec le suivant. Vous pouvez placer une erreur de gestion dans la surcharge de la casse de base en haut: elle sera appelée quand aucun objet ne peut être construit du tout. Nous savons maintenant lequel de Abs1, Abs2 ou quelle que soit la classe de base désirée.
Allons plus loin.

template <class Abs> 
using Factory = std::unique_ptr<Abs>(int, char **); 

template <class Abs> 
using FactoryMap = std::map<std::string, Factory<Abs>*>; 

template <class Abs> 
struct Factories { 
    static const FactoryMap<Abs> map; 
}; 

template <class Abs> 
std::unique_ptr<Abs> makeAbs(int argc, char **argv) { 

    if (argc < 2) 
     return nullptr; 

    return Factories<Abs>::map.at(argv[1])(argc, argv); 
} 

makeAbs chèques et récupère argv[1]. Ensuite, il l'utilise comme une clé dans une carte des fonctions d'usine, pour récupérer l'usine correspondant à ce nom, puis l'appeler et renvoyer l'objet résultant. Si aucun objet de ce nom n'est connu, std::map::at() lancera std::out_of_bounds. Bien sûr, vous pouvez changer cette erreur de manipulation

Maintenant, nous allons voir comment nous pouvons remplir les cartes d'usine, il est en fait assez simple:

template <> 
FactoryMap<Abs1> const Factories<Abs1>::map { 
    {"A", detail::make_abstract_erased<Abs1, A, std::tuple<int, std::string, int>>}, 
    {"B", detail::make_abstract_erased<Abs1, B, std::tuple<int, int>>} 
}; 

Il vous suffit de fournir une définition de FactoryMap<Abs>::map pour chaque Abs vous souhaite utiliser. Comme il s'agit d'une définition d'objet, celle-ci doit être placée dans un fichier .cpp. Notez que, en bonus, vous pouvez ajouter de nouvelles classes et leurs mappings sans recompiler autre chose!

Dernière pièce du puzzle: detail::make_abstract_erased. Vous n'avez pas fourni la déclaration de detail::make_abstract, mais il semble qu'elle renvoie std::unique_ptr<T>, avec T étant son premier argument de modèle.

Étant donné que C++ ne permet pas la conversion entre les pointeurs de fonction qui diffèrent dans les types de retour (et pour de bonnes raisons), nous avons besoin de cette couche supplémentaire juste pour envelopper detail::make_abstract et effectuer la conversion:

namespace detail { 
    template <class Abs, class T, class Params> 
    std::unique_ptr<Abs> make_abstract_erased(int argc, char **argv) { 
     return make_abstract<T, Params>(argc, argv); 
    } 
} 

Et voilà !

See it live on Coliru

+0

Cela ... fonctionne uniquement avec C++ 14, non? T.T – justHelloWorld

+0

Oh merci btw, c'est énorme! : D – justHelloWorld

+0

@justHelloWorld Quoi? Je me suis penché pour qu'il compile en C++ 11 et que vous ne vérifiiez même pas les drapeaux du compilateur sur Coliru? ;) – Quentin