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.
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.
Frequently Asked:
- How to use Unordered_set with User defined classes – Tutorial & Example
- std::unordered_set Basic Usage & Example
- How to initialize an unordered_set in C++11
- How to Search an element in 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; } };
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