Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Design Patterns: Factories – C++

0.00/5 (No votes)
22 Apr 2023CPOL 2.5K  
How to correctly implement design patterns in C++
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:

C++
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:

C++
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> {};

/*WhatShouldWeReturn?*/ 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:

C++
int main() {
    std::variant<int, double, std::string> my_variant;
    my_variant = 15.2;
    auto val = std::get<double>(my_variant);    // 15.2
    auto val2 = std::get<1>(my_variant);        // 15.2
    try {
        std::cout << std::get<int>(my_variant); // my_variant contains double, 
                                                // so this line will throw an exception
    } catch (const std::bad_variant_access&) {}
    return EXIT_SUCCESS;
}

std::visit is a more convenient way to access the values inside a variant:

C++
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) {
        // A single function for all possible types
        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:

C++
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() {
        // Prepare hash table
        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) {
        // Access in O(1), and throw an error if the type can't be deduced.
        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;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)