How to use Unordered_set with User defined classes – Tutorial & Example

In this article we will discuss how to use std::unordered_set with User defined classes.

std::unordered_set internally implements a hash table to store the elements. To know more about Hash Table visit the following article, What is Hashing and Hash Table?

std::unordered_set uses the hasher and comparison function. For primitive data types like int, string etc. default hasher anc comparision functions will work i.e.

  • std::hash<T>()
  • std::equal_to<T>

But for User defined Objects, these default implementations will not work. We need to provide some extra to make this default functions work.

Advertisements

There are 2 ways to make an unordered_set of User Define Types / Classes i.e.

  • Create special functions to make default std::hash<> & std::equals_to<> functions to work with User Defined classes
  • Creating Custom Hasher and Comparision Functors and pass it to unordered_set.

Lets see them one by one. But, first create a Student class i.e.

class Student
{
  int mId;
  std::string mName;

public:
  Student(int id, std::string name) :
      mId(id), mName(name)
  {
  }
  void
  displayInfo()
  {
    std::cout << mId << " :: " << mName << std::endl;
  }
  bool
  operator ==(const Student & obj) const
  {
    if (mId == obj.mId)
      return true;
    else
      return false;
  }

  int
  getId() const
  {
    return mId;
  }
  std::string
  getName() const
  {
    return mName;
  }
};

Unordered_set of User Defined Class with default Hasher & Comparision Function

Lets create an unordered_set of type Student taht should store unique Student objects based on ID.

// Declaring unordered_set of Student
  std::unordered_set<Student> setOfStudents;

Here, we have not provided any Custom Hasher or Comparision function hence default hasher and comparison function will be used i.e.

Default Hasher Function for class Student

std::hash<Student>(const Student & obj);

Default Comparison Function for class Student 

std::equals_to<Student>(const Student & obj1,const Student & obj2 );

This will internally class the == operator() function.

So, to make this unordered_set<Student> work, we need to implement the above hasher and == operator for class Student.

Implementing std::hash<Student>,

namespace std
{
  template<>
    struct hash<Student>
    {
      size_t
      operator()(const Student & obj) const
      {
        return hash<int>()(obj.getId());
      }
    };
}

It will compute the hash on only member variable ID for an object of class Student.

Implementing == operator for class Student,

This will compare the ID only to check if two objects of class Student are equal or not i.e.

  bool operator ==(const Student & obj) const
  {
    if (mId == obj.mId)
      return true;
    else
      return false;
  }

Now we can use the std::unordered_set of type Student with default hasher and comparator i.e.
std::unordered_set<Student> setOfStudents;

// Inserting elements
setOfStudents.insert(Student(11, "John"));
setOfStudents.insert(Student(12, "Harry"));
setOfStudents.insert(Student(13, "Ritz"));
setOfStudents.insert(Student(14, "John"));

// Trying to insert with duplicate ID
// It will not be inserted
auto res = setOfStudents.insert(Student(12, "Varun"));

if (res.second == false)
  std::cout << "Failed to insert with ID 12" << std::endl;

The above Unordered Set will store elements with unique size only.Now, suppose a new requirement comes and we also want to store objects in a new unoredered_set with uniwue names instead of unique ID. How to do that?

unordered_set of User Defined Class with Custom Hasher and Comparator

As, now we need to create a new unordered_set of type Student that can store unique Student objects based on name instead of ID, so we can not use the above implementation of std::hash<Student> and operator==.

[showads ad=inside_post]

 

So, to achieve this we can create custom Hasher and Comparator.

Custom Hasher for class Student

It will compute the hash based on name of Student,

// Custom Hash Functor that will compute the hash on the
// passed string objects length
struct StudentHasher
{
  size_t
  operator()(const Student & obj) const
  {
    return std::hash<std::string>()(obj.getName());
  }
};

Custom Comparator for class Student,

It will compare the two Student objects based on name instead of ID i.e.

// Custom comparator that compares the string objects by length
struct StudentComparator
{
  bool
  operator()(const Student & obj1, const Student & obj2) const
  {
    if (obj1.getName() == obj2.getName())
      return true;
    return false;
  }
};

Now lets use these Custom Functors
  // Declaring unordered_set of Student
  std::unordered_set<Student, StudentHasher, StudentComparator> setOfStudByName;

This set will store the unique student objects based on name only.

Complete working Example is as follows,

#include <iostream>
#include <unordered_set>

class Student
{
  int mId;
  std::string mName;

public:
  Student(int id, std::string name) :
      mId(id), mName(name)
  {
  }
  void
  displayInfo()
  {
    std::cout << mId << " :: " << mName << std::endl;
  }
  bool
  operator ==(const Student & obj) const
  {
    if (mId == obj.mId)
      return true;
    else
      return false;
  }

  int
  getId() const
  {
    return mId;
  }
  std::string
  getName() const
  {
    return mName;
  }
};

namespace std
{
  template<>
    struct hash<Student>
    {
      size_t
      operator()(const Student & obj) const
      {
        return hash<int>()(obj.getId());
      }
    };
}

// Custom Hash Functor that will compute the hash on the
// passed string objects length
struct StudentHasher
{
  size_t
  operator()(const Student & obj) const
  {
    return std::hash<std::string>()(obj.getName());
  }
};

// Custom comparator that compares the string objects by length
struct StudentComparator
{
  bool
  operator()(const Student & obj1, const Student & obj2) const
  {
    if (obj1.getName() == obj2.getName())
      return true;
    return false;
  }
};

int
main()
{
  // Declaring unordered_set of Student
  std::unordered_set<Student> setOfStudents;

  std::cout << "Creating an Unordered_set by unique ID\n";

// Inserting elements
  setOfStudents.insert(Student(11, "John"));
  setOfStudents.insert(Student(12, "Harry"));
  setOfStudents.insert(Student(13, "Ritz"));
  setOfStudents.insert(Student(14, "John"));

// Trying to insert with duplicate ID
  auto res = setOfStudents.insert(Student(12, "Varun"));

  if (res.second == false)
    std::cout << "Failed to insert with ID 12" << std::endl;

  for (Student st : setOfStudents)
    st.displayInfo();

  /*-----------------------------------------------*/

  // Declaring unordered_set of Student
  std::unordered_set<Student, StudentHasher, StudentComparator> setOfStudByName;

  std::cout << "Creating an Unordered_set by Unique Name\n";

  // Inserting elements

  setOfStudByName.insert(Student(11, "John"));
  setOfStudByName.insert(Student(12, "Harry"));
  setOfStudByName.insert(Student(13, "Ritz"));

  res = setOfStudByName.insert(Student(14, "John"));
  if (res.second == false)
    std::cout << "Failed to insert with duplicate name - John" << std::endl;

  // Trying to insert with duplicate ID
  res = setOfStudByName.insert(Student(12, "Varun"));

  if (res.second == false)
    std::cout << "Failed to insert with ID 12" << std::endl;
  else
    std::cout << "Successful in inserting with ID 12 again" << std::endl;

  for (Student st : setOfStudByName)
    st.displayInfo();
  return 0;
}

Output:
Creating an Unordered_set by unique ID
Failed to insert with ID 12
14 :: John
13 :: Ritz
12 :: Harry
11 :: John
Creating an Unordered_set by Unique Name
Failed to insert with duplicate name - John
Successful in inserting with ID 12 again
13 :: Ritz
12 :: Harry
12 :: Varun
11 :: John

 

Do you want to Learn Modern C++ from best?

We have curated a list of Best C++ Courses, that will teach you the cutting edge Modern C++ from the absolute beginning to advanced level. It will also introduce to you the word of Smart Pointers, Move semantics, Rvalue, Lambda function, auto, Variadic template, range based for loops, Multi-threading and many other latest features of C++ i.e. from C++11 to C++20.

Check Detailed Reviews of Best Modern C++ Courses

Remember, C++ requires a lot of patience, persistence, and practice. So, start learning today.

Leave a Comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top