Появилась задача: распарсить конфиг и на его основе инициализировать сложную иерахию полиморфных объектов. Можно было накатать простыню if-else на пару страниц или написать генератор этой самой простыни, но я выбрал велосипединг.
Помимо нежелания катать простыню, сложность заключалась в том, что сначала нужно было определить типы создаваемых объектов, и только после этого можно было определить значения аргументов для их инициализации. Подошло решение в виде фабрики на лямбдах с примесью вариативных шаблонов.
Привожу очень упрощенный вариант:
#include <iostream>
#include <memory>
#include <functional>
#include <map>
template <typename TKey, typename TProduct, typename... Args>
struct Factory {
using Product = std::unique_ptr<TProduct>;
using Lambda = std::function<Product(Args...)>;
using Map = std::map<TKey, Lambda>;
template <typename TConcreteProduct>
static Lambda create;
};
template <typename TKey, typename TProduct, typename... Args>
template <typename TConcreteProduct>
typename Factory<TKey, TProduct, Args...>::Lambda
Factory<TKey, TProduct, Args...>::create = [](Args... args) {
return std::make_unique<TConcreteProduct>(args...);
};
struct Base {
Base(int number, const std::string & message) :
_number(number), _message(message) {}
virtual ~Base() = default;
virtual void print() const = 0;
protected:
int _number;
std::string _message;
};
struct First : Base {
using Base::Base;
virtual void print() const override {
std::cout << "First::print() = " << _number << ", " << _message << "\n";
}
};
struct Second : Base {
using Base::Base;
virtual void print() const override {
std::cout << "Second::print() = " << _number << ", " << _message << "\n";
}
};
struct Third : Base {
using Base::Base;
virtual void print() const override {
std::cout << "Third::print() = " << _number << ", " << _message << "\n";
}
};
using SimpleFactory = Factory<std::string, Base, int, const std::string &>;
static const SimpleFactory::Map factoryMap({
{ "first", SimpleFactory::create<First> },
{ "second", SimpleFactory::create<Second> },
{ "third", SimpleFactory::create<Third> },
});
int main() {
std::cout << "using a specific factory:\n";
auto key = "first";
auto factory = factoryMap.at(key);
auto product = factory(123, "some message");
std::cout << key << ": ";
product->print();
std::cout << "\n";
std::cout << "test all:\n";
for(auto & factoryPair : factoryMap) {
auto product = factoryPair.second(321, "test123");
std::cout << factoryPair.first << ": ";
product->print();
}
}
Код скомпилился, но результат меня сбил с толку. Полночи выискивал багу у себя, а оказалось, что она в компиляторе.
clang 4.0.0-r2:
using a specific factory:
first: Third::print() = 123, some message
test all:
first: Third::print() = 321, test123
second: Third::print() = 321, test123
third: Third::print() = 321, test123
gcc 5.4.0-r3:
using a specific factory:
first: First::print() = 123, some message
test all:
first: First::print() = 321, test123
second: Second::print() = 321, test123
third: Third::print() = 321, test123