上次总结STL容器时有提到,我们不能直接把自定义类型作为关联容器set或map的key。C++编译器会报错的。
如果想这样做,需要对自定义类型规定一个< 运算符。
-
方法1:在自定义类型内定义<运算符。
源代码示例如文章最后。需要注意以下几点。
- bool operator< (const Test_Data & cmp) const 中,参数类型必须是const引用,且函数也得是const函数,否则会报错。
定义了operator<函数后,std::set<Test_Data>和std::map<Test_Data , unsigned int > 可正常使用。std::set<Test_Data> 等同于 std::set<Test_Data, std::less< Test_Data > >
/usr/include/c++/5/bits/stl_function.h:387:20: error: no match for operator< (operand types are const Test_Data and const Test_Data)
{ return __x < __y; }
- Test_Data的唯一一个显式构造函数带参,如果有不带默认值的参数,则Test_Data没有了默认构造函数,这时候,使用Test_Data作为map的value type将会报错。
/usr/include/c++/5/tuple:1172:70: error: no matching function for call to Test_Data::Test_Data()
second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)
- 对于std::map<Test_Data*, unsigned int >,因为Test_Data*是一个指针,而指针是一个C/C++ 内置type,故即使没有定义operator<,也可以正常使用。
- 对于std::set<Test_Data>::iterator it,由于*it的返回值是const (参考《C++ Primer 第5版》P382页,set的关键字是只读的,不可修改),故如果operator<< (std::ostream &os , Test_Data & data) 不加const,会报错。
- 一般来说,const修饰的变量是安全的,没有const修饰的变量是不安全的,一般在传参的时候,非const修饰的的变量可以传给const修饰的,而const 修饰的不可以传给非const修饰的形参,这就相当于把安全的东西交给了不安全的人;而赋值的话更不用说了,const修饰的不可以传给没有const修饰的变量
error: binding const Test_Data to reference of type Test_Data& discards qualifiers
cout<<"["<<i<<"] data "<< *it <<endl
代码中给出了两种operator<的实现,对应的执行结果分别为
-
方法二:
定义一个比较操作符,使用它作为set或map的模板参数,在定义set或map的时候,需要把classcomp 当做参数传进去std::set<Test_Data, classcomp >。
-
方法三
使用函数指针,需要注意的是函数指针的使用,C++11增加了decltype关键字,在使用函数指针时更加方便,但需要在编译时增加-std=c++11的编译选项。(此处可参考《C++ Primer 第5版》P379页)
方法二和三,也可参考 http://www.cplusplus.com/reference/set/set/set/
-
方法四:
为用户自定义类型特化std::less,不是很推荐,实现方法参考
https://www.cnblogs.com/huhu0013/p/4548522.html
在systemC/TLM中,新增的类型sc_time已经实现了operator< ,而tlm::tlm_generic_payload 并没有实现operator<,故其不能作为set/map的的key type。
/*
Original 2020-03-19
README:
This is a example to teach you
how to use a self-defined type as the key in a map
give three methods to realize
execute:
g++ -g -Wall -O0 main.cpp -o sim
if use decltype keyword (C++ 11 feature), you need compile as
g++ -g -Wall -std=c++11 -O0 main.cpp -o sim
*/
#include <iostream>
#include <map>
#include <set>
using namespace std;
class Test_Data
{
public:
//here must let Test_Data have a default constructor,
// if not, you can't use Test_Data as a value type in a map
//we set a default value to all parameter,
// let this constructor as a default constructor
Test_Data(unsigned int year = 0,
unsigned int month = 0,
unsigned int day = 0):
m_year(year),
m_month(month),
m_day(day)
{}
// method 1, you will find 2019,01,01 < 2019,02,02 if use this function
// must add two const keyword here, if not, will compile error
bool operator< (const Test_Data & cmp) const
{
if(m_year != cmp.m_year)
{
return (m_year < cmp.m_year) ;
}
else if (m_month != cmp.m_month)
{
return (m_month < cmp.m_month) ;
}
else
return (m_day < cmp.m_day) ;
}
//method 1, you will find 2019,01,01 > 2019,02,02 if use this function
/*
bool operator< ( const Test_Data & cmp) const
{
if(m_year != cmp.m_year)
{
return (m_year > cmp.m_year) ;
}
else if (m_month != cmp.m_month)
{
return (m_month > cmp.m_month) ;
}
else
return (m_day > cmp.m_day) ;
}
*/
//here overload operator <<, shuold use const reference Test_Data
friend std::ostream& operator<< (std::ostream &os , const Test_Data & data)
{
os<<std::dec <<data.m_year <<"-"
<<data.m_month <<"-"
<<data.m_day;
return os;
}
public:
unsigned int m_year;
unsigned int m_month;
unsigned int m_day;
};
// method 2
struct classcomp
{
bool operator() (const Test_Data & lhs, const Test_Data & cmp) const
{
if(lhs.m_year != cmp.m_year)
{
return (lhs.m_year < cmp.m_year) ;
}
else if (lhs.m_month != cmp.m_month)
{
return (lhs.m_month < cmp.m_month) ;
}
else
return (lhs.m_day < cmp.m_day) ;
}
};
//method 3
bool FunctionComp (const Test_Data & lhs, const Test_Data & cmp)
{
if(lhs.m_year != cmp.m_year)
{
return (lhs.m_year < cmp.m_year) ;
}
else if (lhs.m_month != cmp.m_month)
{
return (lhs.m_month < cmp.m_month) ;
}
else
return (lhs.m_day < cmp.m_day) ;
}
int main(int argc, char** argv)
{
Test_Data a(2019,01,01);
Test_Data b(2019,02,02);
Test_Data c(2019,02,03);
Test_Data d(2020,02,02);
// std::set<Test_Data > m_test_set; // method 1
// std::set<Test_Data, classcomp > m_test_set; // method 2
// method 3 must add -std=c++11 when compile if use decltype
// std::set<Test_Data, decltype(FunctionComp)* > m_test_set (FunctionComp);
// method 3 use function pointer
bool(*fun_ptr)(const Test_Data & , const Test_Data &) = FunctionComp ;
std::set<Test_Data, bool(*)(const Test_Data & , const Test_Data &) > m_test_set (fun_ptr);
m_test_set.insert(a);
m_test_set.insert(b);
m_test_set.insert(c);
m_test_set.insert(d);
std::set<Test_Data>::iterator it;
unsigned int i =0;
for ( it = m_test_set.begin(); it != m_test_set.end(); ++it, ++i)
{
//overload operator << must use const reference Test_Data
//or here will compile error for *it
//because the return value of *it is temporary
cout<<"["<<i<<"] data "<< *it <<endl;
}
// std::map<Test_Data , unsigned int > m_test_map_1; // method 1
// std::map<Test_Data , unsigned int, classcomp > m_test_map_1; // method 2
std::map<Test_Data , unsigned int, decltype(FunctionComp)* > m_test_map_1 (FunctionComp); // method 3
std::map< unsigned int, Test_Data > m_test_map_2;
std::map<Test_Data*, unsigned int > m_test_map_3;
m_test_map_1 [a] = 100;
m_test_map_2 [100] = b;
Test_Data * a_ptr = new Test_Data (2020,10,10) ;
m_test_map_3 [a_ptr] = 200;
return 0;
}