Design Patterns were always different in C++, and a usual use of these patterns in C++ might cause more complications and problems in this language. In this series of articles, I'll show how to correctly implement them in C++. In this article, we’ll see how to build a factory for a template base class.
Factory Design Pattern
A factory defines as a function which should generate the desired object based on some params (the usual usage is a string
parameter which defines the desired class).
A simple example:
class my_interface {};
class impl1 : public my_interface {};
class impl2 : public my_interface {};
class impl3 : public my_interface {};
std::shared_ptr<my_interface> factory(const std::string &desired_impl) {
std::shared_ptr<my_interface> res;
if (desired_impl == "impl1") res = std::make_shared<impl1>();
else if (desired_impl == "impl2") res = std::make_shared<impl2>();
else if (desired_impl == "impl3") res = std::make_shared<impl3>();
else throw std::out_of_range("Desired impl not found.");
return res;
}
int main() {
std::shared_ptr<my_interface> my_impl = factory("impl2");
return EXIT_SUCCESS;
}
Hashing Upgrade
We discussed the way to optimize this factory using a hash table in the previous article: Hash your Conditions – C++.
Template Base Class Factory
Things get complicated when our interface is actually a template
class:
template <typename T> class my_class { T type; };
template <> class my_class<float> { float t1; float t2; };
template <> class my_class<int> { std::vector<int> values; };
template <> class my_class<std::string> {};
factory(const std::string &type) {
if (type == "float") return my_class<float>();
else if (type == "int") return my_class<int>();
else if (type == "string") return my_class<std::string>();
else throw std::out_of_range("Desired impl not found.");
}
This time, we can’t use the pointer solution, because these classes don’t inherit from a shared class and don’t have to have anything in common between them. So, is it possible to implement it at all? The answer for this question is: Yes.
std::variant & std::visit
(Since C++17) std::variant
can contain a single instance of one of a known list of possible types.
For example:
int main() {
std::variant<int, double, std::string> my_variant;
my_variant = 15.2;
auto val = std::get<double>(my_variant); auto val2 = std::get<1>(my_variant); try {
std::cout << std::get<int>(my_variant); } catch (const std::bad_variant_access&) {}
return EXIT_SUCCESS;
}
std::visit
is a more convenient way to access the values inside a variant:
template<class... Ts>
struct multiple_functions_container : Ts... { using Ts::operator()...; };
template<class... Ts>
multiple_functions_container(Ts...) -> multiple_functions_container<Ts...>;
int main() {
std::variant<int, double, std::string> my_variant;
my_variant = 15.2;
std::visit([](auto &&val) {
std::cout << val;
}, my_variant);
my_variant = "My string";
std::visit(multiple_functions_container {
[](auto &&val) { std::cout << "Default function: " << val << "\n"; },
[](int val) { std::cout << "Integer function: " << val << "\n"; },
[](const std::string &val)
{ std::cout << "String function: " << std::quoted(val) << "\n"; }
}, my_variant);
return EXIT_SUCCESS;
}
It’s time to use it in our factory:
template <typename T> class my_class { T type; };
template <> class my_class<float> { float t1; float t2; };
template <> class my_class<int> { std::vector<int> values; };
template <> class my_class<std::string> {};
using possible_classes_variant =
std::variant<my_class<float>, my_class<int>, my_class<std::string>, my_class<double>>;
class factory {
public:
factory() {
hash = {
{"int", my_class<int>()},
{"float", my_class<float>()},
{"string", my_class<std::string>()},
{"double", my_class<double>()}
};
}
possible_classes_variant deduce_type(std::string &&type) {
return hash.at(type);
}
private:
std::unordered_map<std::string, possible_classes_variant> hash;
};
template<class... Ts> struct multiple_functions_container :
Ts... { using Ts::operator()...; };
template<class... Ts> multiple_functions_container(Ts...) ->
multiple_functions_container<Ts...>;
int main() {
factory my_factory;
possible_classes_variant var;
var = my_factory.deduce_type("string");
std::visit(multiple_functions_container {
[](const my_class<std::string> &val) { std::cout << "String class\n"; },
[](const my_class<int> &val) { std::cout << "Int class\n"; },
[](const my_class<double> &val) { std::cout << "Double class\n"; },
[](const my_class<float> &val) { std::cout << "Float class\n"; }
}, var);
return EXIT_SUCCESS;
}