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

Hash Your Conditions – C++

0.00/5 (No votes)
22 Apr 2023CPOL1 min read 1.8K  
Hash your conditions in C++
Conditions are the most natural way to control your code flow in programming, it’s almost the first thing that every programmer learn at the beginning. Sometimes conditions, especially if you are conditioning strings in C++, the code become very complicated to understand and very lazy. Let’s break this pattern.

Legacy Code

You join a new team and start to read the code. After some time reading, you see the following function:

C++
std::shared_ptr<interface> deduce_type(std::string &&type) {
    std::shared_ptr<interface> res;
    if (type == "1") res = std::make_shared<impl1>();
    else if (type == "2") res = std::make_shared<impl2>();
    else if (type == "3") res = std::make_shared<impl3>();
    else if (type == "4") res = std::make_shared<impl4>();
    else if (type == "5") res = std::make_shared<impl5>();
    else if (type == "6") res = std::make_shared<impl6>();
    else if (type == "7") res = std::make_shared<impl7>();
    return res;
}

You start to think to yourself “There must be a better way of doing it… Why not using switch statement?”, and then you remember that switch statements can’t be applied to string elements in C++. So, is there a better way?

Hash Tables

Hash table is a structure which builds from Key and Value, which can be the same or different types. The way hash table works is it takes the key you enter, applies a hash for it and stores it in the result of the hash function place. When you want to access the same place, it applies the same hash function for the key you want to access to and returns the value that exists there.

Lucky us, the standard library takes care of this structure for us – std::unordered_map.

Quick Note

Pay attention to the difference between std::map to std::unordered_map:
std::map – Red-Black tree implementation, using std::less by default (operator< between keys).
std::unordered_map – Hash table implementation. (For complicated keys, you’ll have to implement the hash function for yourself.)

Refactor Conditioning

Let’s replace conditioning using hash tables:

C++
class type_hash {
public:
    type_hash() {
        // Prepare hash table
        hash = {
                {"1", [] { return std::make_shared<impl1>(); }},
                {"2", [] { return std::make_shared<impl2>(); }},
                {"3", [] { return std::make_shared<impl3>(); }},
                {"4", [] { return std::make_shared<impl4>(); }},
                {"5", [] { return std::make_shared<impl5>(); }},
                {"6", [] { return std::make_shared<impl6>(); }},
                {"7", [] { return std::make_shared<impl7>(); }}
        };
    }

    std::shared_ptr<interface> deduce_type(std::string &&type) {
        // Access in O(1), and throw an error if the type can't be deduced.
        return hash.at(type)(); // Call the function which exists in <type> place.
    }

private:
    std::unordered_map<std::string, std::function<std::shared_ptr<interface>()>> hash;
};

std::shared_ptr<interface> core_function(std::string &&type, type_hash &deducer) {
    std::shared_ptr<interface> res;
    res = deducer.deduce_type(std::move(type));
    return res;
}

Go Further!

Assume we want to deduce a function out of closed functions collection:

C++
class functions_collection {
public:
    void func1() { num = 1; print_number(); }
    void func2() { num = 2; print_number(); }
    void func3() { num = 3; print_number(); }
    void func4() { num = 4; print_number(); }
    void func5() { num = 5; print_number(); }
    void func6() { num = 6; print_number(); }
    void func7() { num = 7; print_number(); }

private:
    int num = 0;

    void print_number() {
        std::cout << num << "\n";
    }
};

We need to create a hash collection that is limited to functions only from this class:

C++
class type_hash {
private:
    using functions_collection_t = void (functions_collection::*)();

public:
    type_hash() {
        hash = {
                {"1", &functions_collection::func1},
                {"2", &functions_collection::func2},
                {"3", &functions_collection::func3},
                {"4", &functions_collection::func4},
                {"5", &functions_collection::func5},
                {"6", &functions_collection::func6},
                {"7", &functions_collection::func7},
        };
    }

    functions_collection_t deduce_type(std::string &&type) {
        return hash.at(type);
    }

private:
    std::unordered_map<std::string, void (functions_collection::*)()> hash;
};

void core_function(std::string &&type, type_hash &deducer) {
    // ...
    functions_collection fc;
    auto desired_func = deducer.deduce_type(std::move(type));
    (fc.*desired_func)();
    // ...
}

Share Your Ideas

Have new ways to solve this lazy conditions way? Share them in the comments!

Checkout the GitHub repository with examples: cppsenioreas-hash-your-conditions.

License

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