Search code examples
c++design-patternsencapsulationunordered-mapunordered-set

How to make well-encapsulated classes while using unordered_set/map in c++?


I was looking at some tutorials on how to make an unordered_set for a class/struct. I found this easy-to-understand code (as a Java developer) which does the trick:

#include <iostream>
#include <unordered_set>
using namespace std;

struct Node{
    int val;
  
    bool operator==(const Node& n) const{
        return (this->val == n.val);
    }
};

class HashFunction{
    public:
        size_t operator()(const Node& n) const{
            return n.val;
        }
};

int main(){
    Node n1 = { 1 }, n2 = { 2 },
         n3 = { 3 }, n4 = { 4 };

    unordered_set<Node, HashFunction> us;
    us.insert(n1);
    us.insert(n2);
    us.insert(n3);
    us.insert(n4);
    
    for (auto node : us){
        cout << node.val << " ";
    }
    cout << endl;
    return 0;
}

I was wondering if we can make the struct Node a class, make the int val a private field and add unordered_set<Node, HashFunction> neighbours as a field to the Node class.

If not, what is a good practice to keep classes/structs well-encapsulated and have sets/maps fields for classes?


Solution

  • There are a few questions here, so I'll try to answer them in order:

    can make the struct Node a class

    Yes, struct and class only differ in their default permissions (in struct things are public unless stated otherwise, in class they are private) So this is identical code to what you wrote:

    class Node{
    public:
        int val;
      
        bool operator==(const Node& n) const{
            return (this->val == n.val);
        }
    };
    

    make the int val a private field and add unordered_set<Node, HashFunction> neighbours as a field to the Node class

    Yes, you can. The easiest way I can think of to make that transition from the code that you wrote is to make HashFunction be a sub-class of Node. For example:

    class Node {
        class HashFunction {
            public:
                size_t operator()(const Node& n) const{
                    return n.val;
                }
        };
    
    public:
        Node(int _val) : val(_val) {}
    
        bool operator==(const Node& n) const{
            return (this->val == n.val);
        }
    
        // More methods here
    
    private:
        int val;
        unordered_set<Node, HashFunction> neighbours;
    };
    

    If not, what is a good practice to keep classes/structs well-encapsulated and have sets/maps fields for classes?

    I guess that is somewhat of a mute question in this case - but the general answer is to expose only the minimum required interface. For example, right now HushFunction is aware of the internal content of Node. in order to increase encapsulation we could have added a hush method to Node and have HushFunction invoke that method. This way if the content of Node changes, nothing outside of Node needs to be aware of it.