The key is in what they contain. A set is just what you'd think it is: a set of objects. Use it when you just need to store and find a bunch of things, like student names.
A map is used to store key/value pairs. For example, if you need to look up a student record from a student ID, you might use a map from ID to record.
A set could be a list of student IDs in a class - the same student can't be in a class twice.
A map could be a relationship between student ID and student name - each student can only have one name, but it's ok if two students have the same name (as sometimes happens in real life).
Don't be concerned with the implementation, it is not relevant to your understanding and may not be the same for all compilers.
I gave example use cases in my previous post.
Both containers are useful when you need to maintain unique values. Only one container is useful when you want to associate each unique value with an arbitrary value.
You can freely change the aspects of the values in a set so long as they do not affect the sort order. If you change the values in a way that affects the sort order, you get undefined behavior. It's like having a sorted bookshelf and changing one of the book's names - the bookshelf doesn't know you did that and it might never check.
Implementing sets or maps as binary search trees isn't mandatory. There are many other ways to do it. F.e. hash tables may do a good job in some cases. Or you may even use a list or an array, ... But it may be expensive to ensure uniqueness in lists and arrays. So they may be useful if the set or map will contain only a few items (f.e. in small embedded systems with poor memory resources).
There may be thousands of use cases for sets and maps:
1 2 3 4 5
//Map absolute magnitude of some stars on the sky
typedeffloat Magnitude;
class Star { /* ... */ };
typedef map<Star*, Magnitude> Star2Magnitude;
// A set of stars having an attribute named `magnitude´ may do it similar if the comparison
// operator compares two stars by their `magnitude´.
// NOTE: The concept of a set (like `std::set´) forces uniqueness of its items. So there
// cannot exists two stars with the same magnitude in it! (But have a look at
// `std::multiset´)
typedefunsignedlong Magnitude;
class Star
{
private:
Magnitude _magnitude; // A stars key attribute. Should NEVER be modified.
// It cannot be declared `const' due to loss of the
// copy constructor in case of a `const' attribute.
// ...
public:
Star(Magnitude theMagnitude = 0) :
_magnitude(theMagnitude)
{}
// Only define a getter.
// Never modify _magnitude as long as the Star is member of a set.
Magnitude magnitude()
{ return _magnitude; }
// A Stars comparison operator uses its key attribute
booloperator<(const Star &right) const
{ return _magnitude < right._magnitude; }
// ...
};
typedef set<Star> Stars;
// Looking for a Star by magnitude is also a little bit more difficult.
staticbool lookupStarByMagnitude(const Stars &stars, Magnitude magnitude,
Star &starFound)
{
const Star rightStar(magnitude);
const Stars::iterator lookupResult = stars.find(rightStar);
constbool found = (lookupResult != stars.end());
if (found)
{
starFound = *lookupResult;
}
return found;
} /* lookupStarByMagnitude() */
1 2 3 4 5 6
// A galaxy contains some stars
class Star { /* ... */ };
typedef set<Star*> Stars;
Stars galaxy;
Note that a set of pointers or a map with pointers as the key only guarantees uniqueness of memory location (unless you provide a specialized comparison operator).
The reference doc for unordered_set on this site writes:
unordered_set containers are faster than set containers to access individual elements by their key, although they are generally less efficient for range iteration through a subset of their elements.
There are trade-offs. How big is the difference? Depends on the use case.
Think of a linked list that has numbers in ascending order. You can insert and remove numbers, but if you would want to change a number that would probably require reordering of the nodes in the list, which in turn is easiest to do by remove(old) + insert(new). But why provide a change(old, new) when the user can do the same with existing insert() and remove() with relative ease?
However, it is really easy to loop over the first N (smallest) values of the list.
Lets do something different. Have ten buckets 0..9. Put all values that start with digit k into bucket k. That is quick and simple. Again, changing a value probably means that it cannot stay in the same bucket. In addition, it does take more bookkeeping, if you want to remember in which order the values were inserted into the buckets.
In order to change a value already in a set, the value in the set has to be be erased and replaced with the new value. Similarly you can't directly change the key in a map without creating problems.
"Note that a set of pointers or a map with pointers as the key only guarantees uniqueness of memory location (unless you provide a specialized comparison operator)."
Its very true. We need to override comparison operator in above case.