From: https://www.walletfox.com/course/mapwithcustomclasskey.php

If you have ever tried to use a custom class as a key of std::map, most probably you got a compilation error. This article explains why this happens and shows how to make custom classes work as keys of std::map by providing a sorting rule. The article demonstrates three different ways in which you can provide the sorting rule, namely via operator overloading, a comparator (C++98) or with help of a lambda expression (C++11 only).

The reason why we have to provide a sorting rule for user-defined classes as keys is because std::map is a binary search tree data structure. This videoshows how binary search trees work in general . Binary search trees store their data in sorted order, thus we have to provide a sorting rule. Once we provided the sorting rule, the map can make use of binary search for all its operations. A binary tree structure for a map of size 6 can be seen below:

A basic example of a binary search can be a guessing game in which one of the players thinks of a number in a certain range (e.g. 0 - 24) and the other player tries to guess it. In binary search the second player always guesses the middle element (i.e. 12) and the first player informs him whether this is larger, smaller or equal to the value that he thought of. Every time, the player makes a guess, one half of the array gets eliminated. The search is repeated until the second player guesses the correct number. The number of times we need to do this is log2(N) because each comparison throws out half of the possibilities.

Problem definition

Imagine we have a class called Color represented by three private attributes red, green and blue and we would like to use this class as a key of std::map. The class can be seen below. Notice that I also overloaded operator<<, this has nothing to do with our problem, it is there just so that we can print the Color directly with std::cout.

class Color{
public:
Color();
Color(int r, int g, int b);
int red() const {return m_red;}
int green() const {return m_green;}
int blue() const {return m_blue;}
bool areValid() const;
private:
int m_red;
int m_green;
int m_blue;
}; std::ostream& operator<<(std::ostream &output, const Color &c);

Let's look at the main.cpp below. We construct a map in which we use our class Color as a key and an int as a value. We insert a couple of pairs (Color, int) and try to print the elements of the map.

int main()
{
std::map<Color, int> myMap;
myMap[Color(0, 0, 0)] = 0;
myMap[Color(230, 159, 0)] = 1;
myMap[Color(86, 180, 233)] = 2;
myMap[Color(128, 128, 0)] = 3; std::map<Color, int>::iterator it;
for (it = myMap.begin(); it != myMap.end(); ++it)
std::cout << it->first << " " << it->second << '\n'; return 0;
}

If we try to compile the code above, it won't compile and produces the following error: no match for 'operator<' in '__x < __y'. This is because we did not provide a rule to sort the elements which are needed by the binary search tree. How do we solve this? The following lines show how to solve the problem.

Overloading the operator< (C++98)

The first possible solution to our problem is overloading operator<. This is highlighted in the code below. The overloaded operator< takes references to two color instances as arguments and returns true or false based on our sorting rule which will appear in the body of the method. Notice that the only thing we need to do is to provide a rule according to which to compare two color objects. This is sufficient for the std::map to achieve sorted order.

class Color{
public:
Color();
Color(int r, int g, int b);
int red() const {return m_red;}
int green() const {return m_green;}
int blue() const {return m_blue;}
bool areValid() const;
private:
int m_red;
int m_green;
int m_blue;
}; std::ostream& operator<<(std::ostream &output, const Color &c);
bool operator<(const Color &c1, const Color &c2);

Now let's look at the body of the overloaded operator from color.cpp (the entire source files can be found above). How do we decide which color is "smaller"? This depends on our intentions. If we only require that our custom class works with std::map, we should provide something simple and logical. We might choose to sort colors according to their lightness which is in its simplest case an average of red, green and blue. Thus, the operator< will return true if the average of R,G and B of the left-hand color is smaller than the average of R, G and B of the right-hand color. This can be seen below

bool operator<(const Color &c1, const Color &c2){
return c1.red() + c1.green() + c1.blue() <
c2.red() + c2.green() + c2.blue();
}

Note: The basic formula for color lightness is an average of R, G and B, i.e. (R + G + B) / 3. Notice that we omitted the division by 3 in the code above, that is because when comparing two colors the division by 3 simply disappears from the equation.

Note: The code compiles and produces the following output: You can see that the colors are sorted in ascending order according to the sum of their R,G and B values.

(0, 0, 0) 0
(128, 128, 0) 3
(230, 159, 0) 1
(86, 180, 233) 2

Note: Notice that we could call

std::cout << it->first << " " << it->second << '\n';

only because we also overloaded the output operator<<. The details of this can be found in the source files above.

User-defined comparator (C++98)

Another way of providing the sorting rule is with the help of a custom comparator. A custom comparator is a function object and a function object is simply a class that defines operator() and as a result can be called as if it was a function. You can see our version of the class below. Notice that the parameters, return type as well as the body of the function are the same as in the first solution.

class Color{
public:
Color();
Color(int r, int g, int b);
int red() const {return m_red;}
int green() const {return m_green;}
int blue() const {return m_blue;}
bool areValid() const;
private:
int m_red;
int m_green;
int m_blue;
}; std::ostream& operator<<(std::ostream &output, const Color &c); class Comparator {
public:
bool operator()(const Color& c1, const Color& c2){
return c1.red() + c1.green() + c1.blue() <
c2.red() + c2.green() + c2.blue();
}
};

To use the comparator, notice that we passed an extra argument to the map. The rest of the code remains the same.

int main()
{
std::map<Color, int, Comparator> myMap;
myMap[Color(0, 0, 0)] = 0;
myMap[Color(230, 159, 0)] = 1;
myMap[Color(86, 180, 233)] = 2;
myMap[Color(128, 128, 0)] = 3; std::map<Color, int>::iterator it;
for (it = myMap.begin(); it != myMap.end(); ++it)
std::cout << it->first << " " << it->second << '\n'; return 0;
}

Lambda expression (C++11)

C++11 provides another solution to the same problem, namely a lambda expression.

A lambda expression is a syntactic shortcut for a function object, i.e. an object that can be called as if it was a function. The basic syntax of the lambda expression can be seen below:

[captures] (parameter list) -> return-type
{
lambda body;
}

The lambda expression for our problem can be seen below. Our lambda expression has no captures, takes two parameters and returns a bool. The body of the expression also remains the same as in the previous solutions.

auto comparator = [](const Color& c1, const Color& c2) -> bool
{
return c1.red() + c1.green() + c1.blue() <
c2.red() + c2.green() + c2.blue();
};

Notice that to use the lambda expression with our map we have to use decltype. The decltype() is here because we cannot use lambda in unevaluated context. We firstly have to define lambda with 'auto' elsewhere and then only use it in the map's parameters with decltype(). If we did not do this, we would get a compilation error that would look like this: type/value mismatch at argument 3 in template parameter list for 'template<class _Key, class _Tp, class _Compare, class _Alloc> class std::map'.

int main()
{
auto comparator = [](const Color& c1, const Color& c2) -> bool
{
return c1.red() + c1.green() + c1.blue() <
c2.red() + c2.green() + c2.blue();
}; std::map<Color, int, decltype(comparator)> myMap(comparator);
myMap = {
{Color(0, 0, 0), 0},
{Color(230, 159, 0), 1},
{Color(86, 180, 233), 2},
{Color(128, 128, 0), 3}
}; for (auto& it : myMap)
std::cout << it.first << " " << it.second << '\n'; return 0;
}

That's it. Now the std::map will work with our user-defined class.

Using std::map with a custom class key的更多相关文章

  1. std::map插入已存在的key时,key对应的内容不会被更新

    std::map插入已存在的key时,key对应的内容不会被更新,如果不知道这一点,可能会造成运行结果与预期的不一致 “Because element keys in a map are unique ...

  2. c++ how to make your own class a valid key type for std::map?

    In Java, if you want your own class to be a valid key type of the container, you just need to make i ...

  3. std::map自定义类型key

    故事背景:最近的需求需要把一个结构体struct作为map的key,时间time作为value,定义:std::map<struct, time> _mapTest; 技术调研:众所周知, ...

  4. C++ std::map

    std::map template < class Key, // map::key_type class T, // map::mapped_type class Compare = less ...

  5. std::map用法

    STL是标准C++系统的一组模板类,使用STL模板类最大的好处就是在各种C++编译器上都通用.    在STL模板类中,用于线性数据存储管理的类主要有vector, list, map 等等.本文主要 ...

  6. C++ std::map::erase用法及其陷阱

    1.引入: STL的map中有一个erase方法用来从一个map中删除制定的节点 eg: map<string,string> mapTest; typedef map<string ...

  7. std::map的操作:插入、修改、删除和遍历

    using namespace std; std::map<int,int> m_map; 1.添加 for(int i=0;i<10;i++) { m_map.insert(mak ...

  8. map以自定义类型当Key

    关于map的定义: template < class Key, class T, class Compare = less<Key>, class Allocator = alloc ...

  9. 对std::map进行排序

    1.对Key排序. std::map的第三个参数即为对key进行排序的比较函数.默认为less,表示升序.如果要降序,可以改为greater. 2.对Value排序 不支持,因为map不是一个序列的容 ...

随机推荐

  1. LeetCode(31): 下一个排列

    Medium! 题目描述: (请仔细读题) 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列. 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列) ...

  2. cf776c

    这题用尺取法是怼不出来的... 一开始看到区间和等于k的幂,并且有负数,首先想到将前缀和排序后用尺取法,但因为排序后的前缀和次序是乱的,只适用带绝对值的情况(poj2566),所以无法做. 看了题解后 ...

  3. python 全栈开发,Day95(RESTful API介绍,基于Django实现RESTful API,DRF 序列化)

    昨日内容回顾 1. rest framework serializer(序列化)的简单使用 QuerySet([ obj, obj, obj]) --> JSON格式数据 0. 安装和导入: p ...

  4. python 全栈开发,Day26(hashlib文件一致性,configparser,logging,collections模块,deque,OrderedDict)

    一.hashlib文件一致性校验 为何要进行文件一致性校验? 为了确保你得到的文件是正确的版本,而没有被注入病毒和木马程序.例如我们经常在网上下载软件,而这些软件已经被注入了一些广告和病毒等,如果不进 ...

  5. A. 【UNR #2】UOJ拯救计划

    题解: 感觉多了解一些npc问题是很有用的.. 就不会像我一样完全不考虑模数的性质 前面60分大概是送分 后面主要考虑一下%6带来的影响 平常都是那么大的模数,突然这么小??? 考虑正好使用k种颜色的 ...

  6. python学习之条件语句(if循环)

    Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块.可以通过下图来简单了解条件语句的执行过程: Python程序语言指定任何非0和非空(null)值为tru ...

  7. Codeforces Round #321 (Div. 2) E - Kefa and Watch

    题目大意:给你一个由0-9组成的字符串,有m个询问,两种操作,第一种将l到r的字符全部变成c,第二种问l到r这段 字符串的循环节是不是d. 思路:首先我们要知道怎么判断字符串的循环节的长度是不是d,如 ...

  8. tarjan 算法求强连通分量

    #include<bits/stdc++.h> #define ll long long using namespace std; const int P=1e6; ; ; const i ...

  9. PHP函数比较变量

    作为一个PHPer,一定要多看PHP手册,每过一遍手册,你总会发现一些以前被你忽略的一些东西:我们可以从各个不同的地方/渠道学习到PHP的相关知识,但也不要忘记我们的手册,PHP手册永远是学习和提升P ...

  10. 枚举 enumerate 让列表有序号

    把列表转字典,或序列对: