2017-01-03 9 views
1

У меня есть два абстрактных класса C++ Abs1 и Abs2. Тогда у меня есть:Создание общего объекта из командной строки

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

Теперь я пытаюсь create objects from command line arguments, и я должен сделать переписать функцию общественной заводской make_abstract в связанном вопросе, что-то вроде:

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); 
    } 
} 

Как вы можете видеть это ужасно избыточно. Как я могу сделать общий вариант этого? В этой версии мы можем передать столько реализованного класса, сколько хотим, поэтому каскад if не является решением. Обратите внимание, что мы не можем изменить какой-либо из этих классов.

Я думал, что, может быть, VARIADIC шаблоны могли бы помочь, но я не могу понять, много проблем:

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

Вы не можете просто впихнуть несвязанных типов в тип возвращаемого значения. Как бы это выглядело на сайте вызова? Либо должен быть общий базовый класс, либо союз (или std :: any и т. Д.), Чтобы сделать его пригодным для использования. – Caleth

ответ

2

Оооо, это было весело :)

[TL; DR: есть живой пример внизу]

Я реализовал два слоя картографирования поверх вашей функции (0) detail::make_abstract. Давайте начнем с кода вызова:

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

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

Здесь мы вызываем makeEverything с argc, argv, и список std::unique_ptr с. После завершения функции один из указателей будет содержать объект с правильным типом.

Пойдем глубже.

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...); 
} 

Это ваш обычный рекурсивный шаблон VARIADIC функции: взять первый указатель, попытаться построить объект для него. Если это не удается, выбросьте его и повторите попытку следующего. Вы можете поместить некоторую ошибку в перегрузку базового блока вверху: она будет вызываться, когда объект вообще не может быть сконструирован.

Итак, теперь мы знаем, какой из Abs1, Abs2 или любой желаемый базовый класс.
Пойдем глубже.

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 проверяет и извлекает argv[1]. Затем он использует его в качестве ключа в карте заводских функций, чтобы получить фабрику, соответствующую этому имени, а затем вызвать ее и вернуть результирующий объект.

Если объект с таким названием не известен, std::map::at() будет кидать std::out_of_bounds. Конечно, вы можете изменить эту ошибку обработки

Теперь давайте посмотрим, как мы можем заполнить заводские карты, это на самом деле довольно легко:

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>>} 
}; 

Вы просто должны дать определение FactoryMap<Abs>::map для каждого Abs вы желающий использовать. Поскольку это определение объекта, это должно быть помещено в файл .cpp. Обратите внимание, что в качестве бонуса вы можете добавлять новые классы и их сопоставления без перекомпиляции чего-либо еще!

Заключительный кусочек головоломки: detail::make_abstract_erased. Вы не указали объявление detail::make_abstract, но похоже, что оно возвращает std::unique_ptr<T>, а T является его первым аргументом шаблона.

Учитывая, что C++ не допускает преобразования между указателями на функции, которые различаются по типам возврата (и по понятным причинам), нам нужно, что дополнительный слой просто обернуть detail::make_abstract и выполнить преобразование:

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); 
    } 
} 

И это все !

See it live on Coliru

+0

Это ... работает только с C++ 14, правильно? T.T – justHelloWorld

+0

Oh thx btw, это массивный! : D – justHelloWorld

+0

@justHelloWorld Что ?? Я сгибался, поэтому компилируется в C++ 11, и вы даже не проверяете флаги компилятора на Coliru? ;) – Quentin