STL 关联容器具有键的概念,尽管形式上还比较初级。因此,这类容器的键由嵌套类型 key_type
标识;对于 std::set
和 std::multiset
,key_type
与 value_type
一致,即键就是元素本身。std::map
和 std::multimap
管理类型为 std::pair<const Key,T>
的元素,其中第一个成员是键。在这两种情况下,从给定元素获取键的过程是隐式固定的,用户无法自定义。
像 STL 关联容器那样固定的键提取机制在 Boost.MultiIndex 的上下文中扩展性不佳,因为在 Boost.MultiIndex 中,多个索引共享它们的 value_type
定义,但可能具有完全不同的查找语义。因此,Boost.MultiIndex 将 Key Extractor
的概念形式化,以便在基于键的索引定义中使其显式化并可控。
直观地说,键提取器是一个函数对象,它接受对元素的引用并返回与之关联的键。形式概念还对稳定性施加了一些合理的约束,即提取器假定在接收到相同元素时返回相同的键:这与键实际上是元素“一部分”且不依赖于外部数据的非正式理解是一致的。
如果键提取器在接收到非 const 元素时返回一个非 const 键的引用,则称其为读/写键提取器;否则称为只读键提取器。当使用基于键的索引的 modify_key
成员函数时,Boost.MultiIndex 要求键提取器是读/写的。在所有其他情况下,只读提取器就足够了。关于 Boost.MultiIndex 键提取器的进阶功能 的部分展示了预定义键提取器是读/写的情况。
identity
identity
键提取器将整个基对象作为关联键返回。
#include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/identity.hpp> multi_index_container< int, indexed_by< ordered_unique< identity<int> // the key is the entire element > > > cont;
member
member
键提取器返回对基对象指定数据字段的引用。例如,在我们熟悉的员工容器的以下版本中:
#include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/identity.hpp> #include <boost/multi_index/member.hpp> typedef multi_index_container< employee, indexed_by< ordered_unique<identity<employee> >, ordered_non_unique<member<employee,std::string,&employee::name> >, ordered_unique<member<employee,int,&employee::ssnumber> > > > employee_set;
第二个和第三个索引分别使用 employee::name
和 employee::ssnumber
上的 member
提取器。member
实例化的规范简单但稍显繁琐:
member<(base type),(key type),(pointer to member)>
第一个和第二个参数似乎是多余的,因为基对象和关联数据字段的类型已经隐含在成员指针参数中:不幸的是,使用当前的 C++ 机制无法提取此信息,这使得 member
的语法稍显冗长。
const_mem_fun
和 mem_fun
有时,索引的键不是元素的具体数据成员,而是由特定成员函数返回的值。这类似于某些关系数据库支持的计算索引的概念。Boost.MultiIndex 通过 const_mem_fun
支持这种键提取。考虑下面的容器,其中第三个索引的排序基于姓名的长度:
#include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/identity.hpp> #include <boost/multi_index/member.hpp> #include <boost/multi_index/mem_fun.hpp> struct employee { int id; std::string name; employee(int id,const std::string& name):id(id),name(name){} bool operator<(const employee& e)const{return id<e.id;} // returns the length of the name field std::size_t name_length()const{return name.size();} }; typedef multi_index_container< employee, indexed_by< // sort by employee::operator< ordered_unique<identity<employee> >, // sort by less<string> on name ordered_non_unique<member<employee,std::string,&employee::name> >, // sort by less<int> on name_length() ordered_non_unique< const_mem_fun<employee,std::size_t,&employee::name_length> > > > employee_set;
const_mem_fun
的用法语法与 member
类似。
const_mem_fun<(base type),(key type),(pointer to member function)>
引用的成员函数必须是 const
的,不接受任何参数,并返回指定键类型的值。几乎总是会使用 const
成员函数,因为 multi_index_container
中的元素被视为常量,就像 std::set
的元素一样。但是,提供了一个 mem_fun
对等体用于非 const 成员函数,其适用性在关于 Boost.MultiIndex 键提取器的进阶功能 的段落中进行了讨论。
在示例部分的 示例 2 中提供了一个完整的程序,展示了如何使用 const_mem_fun
。
考虑以下无法编译的代码:
struct employee { ... std::size_t salary()const&; // note the & }; typedef multi_index_container< employee, indexed_by< ... ordered_non_unique< // compiler error: can't convert &employee::salary to // std::size_t (employee::*)() const const_mem_fun<employee,std::size_t,&employee::salary> > > > employee_set;
这里的问题在于 &employee::salary
的类型是 ref-qualified 的,这与 const_mem_fun
期望的完全不匹配。幸运的是,Boost.MultiIndex 提供了一个变体来适应:
typedef multi_index_container< employee, indexed_by< ... ordered_non_unique< cref_mem_fun<employee,std::size_t,&employee::salary> > > > employee_set;
这是所有可用的 const_mem_fun
和 mem_fun
变体的列表:
成员函数示例 | 合适的提取器 | 行为类似 |
---|---|---|
int f()const volatile |
cv_mem_fun <int,X,&X::f> |
const_mem_fun |
int f()const& |
cref_mem_fun <int,X,&X::f> |
|
int f()const volatile& |
cvref_mem_fun <int,X,&X::f> |
|
int f()volatile |
volatile_mem_fun <int,X,&X::f> |
mem_fun |
int f()& |
ref_mem_fun <int,X,&X::f> |
|
int f()volatile& |
vref_mem_fun <int,X,&X::f> |
global_fun
虽然 const_mem_fun
及其变体基于基类型的一个给定成员函数来提取键,但 global_fun
接受一个全局函数(或静态成员函数),该函数以基类型作为参数并返回键。
#include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/global_fun.hpp> struct rectangle { int x0,y0; int x1,y1; }; unsigned long area(const rectangle& r) { return (unsigned long)(r.x1-r.x0)*(r.x1-r.x0)+ (unsigned long)(r.y1-r.y0)*(r.y1-r.y0); } typedef multi_index_container< rectangle, indexed_by< // sort by increasing area ordered_non_unique<global_fun<const rectangle&,unsigned long,&area> > > > rectangle_container;
global_fun
的规范遵循以下语法:
global_fun<(argument type),(key type),(pointer to function)>
其中参数类型和键类型必须精确匹配所用函数的签名;例如,上面的示例中,参数类型是 const rectangle&
,不省略 "const
" 和 "&
" 部分。因此,虽然大多数情况下基类型可以按 const 引用接受,但 global_fun
也准备好接受按值或按非 const 引用接受其参数的函数:后者通常不能直接用于 multi_index_container
的规范中,因为 multi_index_container
中的元素被视为常量,但 Boost.MultiIndex 键提取器的进阶功能 部分描述了基于具有非 const 引用参数的此类函数的键提取的有效用例。
在示例部分的 示例 2 使用了 gobal_fun
。
尽管 Boost.MultiIndex 提供的 预定义键提取器 旨在满足大多数情况,但用户也可以在更特殊的场合提供自己的键提取器,只要它们符合 Key Extractor
概念。
// some record class struct record { boost::gregorian::date d; std::string str; }; // extracts a record's year struct record_year { // result_type typedef required by Key Extractor concept typedef boost::gregorian::greg_year result_type; result_type operator()(const record& r)const // operator() must be const { return r.d.year(); } }; // example of use of the previous key extractor typedef multi_index_container< record, indexed_by< ordered_non_unique<record_year> // sorted by record's year > > record_log;
在示例部分的 示例 6 中,在通过指针访问键的复杂场景中应用了一些用户自定义键提取器。
在关系数据库中,复合键依赖于给定表的两个或多个字段。Boost.MultiIndex 中的类似概念通过 composite_key
来建模,如示例所示:
#include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/member.hpp> #include <boost/multi_index/composite_key.hpp> struct phonebook_entry { std::string family_name; std::string given_name; std::string phone_number; phonebook_entry( std::string family_name, std::string given_name, std::string phone_number): family_name(family_name),given_name(given_name),phone_number(phone_number) {} }; // define a multi_index_container with a composite key on // (family_name,given_name) typedef multi_index_container< phonebook_entry, indexed_by< // non-unique as some subscribers might have more than one number ordered_non_unique< composite_key< phonebook_entry, member<phonebook_entry,std::string,&phonebook_entry::family_name>, member<phonebook_entry,std::string,&phonebook_entry::given_name> > >, ordered_unique< // unique as numbers belong to only one subscriber member<phonebook_entry,std::string,&phonebook_entry::phone_number> > > > phonebook;
composite_key
接受对同一值(此处为 phonebook_entry
)的两个或多个键提取器。复合键的查找操作通过传递具有搜索值的元组来完成:
phonebook pb; ... // search for Dorothea White's number phonebook::iterator it=pb.find(std::make_tuple("White","Dorothea")); std::string number=it->phone_number;
复合键按字典顺序排序,即按第一个键排序,如果第一个键相等,则按第二个键排序,依此类推。这种顺序允许部分搜索,其中仅指定第一个键:
phonebook pb; ... // look for all Whites std::pair<phonebook::iterator,phonebook::iterator> p= pb.equal_range(std::make_tuple("White"));
作为记法上的便利,当仅指定第一个键时,可以不将其包含在元组中直接传递参数:
phonebook pb; ... // look for all Whites std::pair<phonebook::iterator,phonebook::iterator> p=pb.equal_range("White");
另一方面,不允许进行不指定第一个键的部分搜索。
默认情况下,为复合键的每个子键使用相应的 std::less
谓词。可以使用 composite_key_compare
指定备用的比较谓词:
// phonebook with given names in reverse order typedef multi_index_container< phonebook_entry, indexed_by< ordered_non_unique< composite_key< phonebook_entry, member<phonebook_entry,std::string,&phonebook_entry::family_name>, member<phonebook_entry,std::string,&phonebook_entry::given_name> >, composite_key_compare< std::less<std::string>, // family names sorted as by default std::greater<std::string> // given names reversed > >, ordered_unique< member<phonebook_entry,std::string,&phonebook_entry::phone_number> > > > phonebook;
有关 composite_key
的应用,请参阅示例部分的 示例 7。
复合键也可以以直接的方式与散列索引一起使用:
struct street_entry { // quadrant coordinates int x; int y; std::string name; street_entry(int x,int y,const std::string& name):x(x),y(y),name(name){} }; typedef multi_index_container< street_entry, indexed_by< hashed_non_unique< // indexed by quadrant coordinates composite_key< street_entry, member<street_entry,int,&street_entry::x>, member<street_entry,int,&street_entry::y> > >, hashed_non_unique< // indexed by street name member<street_entry,std::string,&street_entry::name> > > > street_locator; street_locator sl; ... void streets_in_quadrant(int x,int y) { std::pair<street_locator::iterator,street_locator::iterator> p= sl.equal_range(std::make_tuple(x,y)); while(p.first!=p.second){ std::cout<<p.first->name<<std::endl; ++p.first; } }
请注意,哈希已自动处理:boost::hash
被特化为将复合键作为其元素 boost::hash
值的函数来计算哈希。如果我们为复合键的元素指定不同的哈希函数,我们可以通过使用 composite_key_hash
工具来显式实现:
struct tuned_int_hash { int operator()(int x)const { // specially tuned hash for this application } }; typedef multi_index_container< street_entry, indexed_by< hashed_non_unique< // indexed by quadrant coordinates composite_key< street_entry, member<street_entry,int,&street_entry::x>, member<street_entry,int,&street_entry::y> >, composite_key_hash< tuned_int_hash, tuned_int_hash > >, hashed_non_unique< // indexed by street name member<street_entry,std::string,&street_entry::name> > > > street_locator;
此外,复合键的相等性可以通过 composite_key_equal_to
进行调整,尽管在大多数情况下,默认的相等谓词(依赖于元素类型的 std::equal_to
实例化)将是正确的选择。
与有序索引不同,我们不能通过仅指定复合键的第一个元素来进行部分搜索。
// try to locate streets in quadrants with x==0 // compile-time error: hashed indices do not allow such operations std::pair<street_locator::iterator,street_locator::iterator> p= sl.equal_range(std::make_tuple(0));
这个限制的原因是合乎逻辑的:由于复合键的哈希值取决于其所有元素,因此不可能从部分信息中计算出来。
C++17 引入了 auto
模板参数的声明,这可以用来消除 Boost.MultiIndex 预定义键提取器规范中的一些冗余。例如,与传统的写法相比:
现在可以这样写:typedef multi_index_container< employee, indexed_by< ordered_unique<identity<employee> >, ordered_non_unique<member<employee,std::string,&employee::name> >, ordered_non_unique< const_mem_fun<employee,std::size_t,&employee::name_length> > > > employee_set;
#include <boost/multi_index/key.hpp> ... typedef multi_index_container< employee, indexed_by< ordered_unique<identity<employee>>, ordered_non_unique<key<&employee::name>>, ordered_non_unique<key<&employee::name_length>> > > employee_set;
这会产生完全相同的定义类型,因为 key
构造只是旧语法的别名。key
可用于简化 member
、const_mem_fun
(及其 变体)、mem_fun
(及其 变体)、global_fun
的规范,并且能更简洁地使用 composite_key
。
在此示例中,首次使用typedef multi_index_container< phonebook_entry, indexed_by< // composite key on family name and given name ordered_non_unique< key<&phonebook_entry::family_name,&phonebook_entry::given_name> >, // unique index on phone number ordered_unique<key<&phonebook_entry::phone_number>> > > phonebook;
key
替代了明显更繁琐的写法:请注意,我们甚至不必指定第一个composite_key< phonebook_entry, member<phonebook_entry,std::string,&phonebook_entry::family_name>, member<phonebook_entry,std::string,&phonebook_entry::given_name> >
phonebook_entry
参数:key
的内部机制会自动为我们推断出来。有关技术细节,请查阅 参考。Key Extractor
概念允许同一个对象通过 suitably defined 的 operator()
重载,从几个不同的类型中提取键。
// example of a name extractor from employee and employee * struct name_extractor { typedef std::string result_type; const result_type& operator()(const employee& e)const{return e.name;} result_type& operator()(employee* e)const{return e->name;} }; // name_extractor can handle elements of type employee... typedef multi_index_container< employee, indexed_by< ordered_unique<name_extractor> > > employee_set; // ...as well as elements of type employee * typedef multi_index_container< employee*, indexed_by< ordered_unique<name_extractor> > > employee_ptr_set;
Boost.MultiIndex 提供的预定义键提取器充分利用了这一可能性,使得定义元素是指向实际对象的指针或引用的 multi_index_container
更加简单。以下定义了一个指向员工的指针的 multi_index_container
,并按姓名排序。
typedef multi_index_container< employee *, indexed_by< ordered_non_unique<member<employee,std::string,&employee::name> > > > employee_set;
请注意,这与定义实际 employee
对象的 multi_index_container
的方式完全相同:member
负责必要的额外解引用以访问 employee::name
。为与 Boost.Ref 中的引用包装器互操作,提供了类似的功能。
typedef multi_index_container< boost::reference_wrapper<const employee>, indexed_by< ordered_non_unique<member<employee,std::string,&employee::name> > > > employee_set;
实际上,对指针的支持进一步扩展,以接受我们称之为链式指针的内容。链式指针通过归纳法定义为指向实际元素的原始指针或智能指针或迭代器,指向元素的引用包装器,或指向另一个链式指针;也就是说,链式指针是指针类类型的任意组合,最终解引用到要从中提取键的元素。指向 employee
的链式指针示例如下:
employee *
,const employee *
,std::unique_ptr<employee>
,std::list<boost::reference_wrapper<employee> >::iterator
,employee **
,boost::shared_ptr<const employee *>
.multi_index_container
构建“视图”作为 multi_index_container
的框架中。为了简明扼要地总结在存在引用包装器和指针的情况下 Boost.MultiIndex 键提取器的不同用法,请考虑以下最终类型:
struct T { int i; const int j; int f()const; int g(); static int gf(const T&); static int gg(T&); };
下表列出了对于基于 T
的各种指针和引用包装器类型,针对其每个成员的适当键提取器。
元素类型 | 键 | 键提取器 | 适用于const 元素? |
读/写? |
---|---|---|---|---|
T |
i |
member<T,int,&T::i> |
是 | 是 |
j |
member<T,const int,&T::j> |
是 | 否 | |
f() |
const_mem_fun<T,int,&T::f> |
是 | 否 | |
g() |
mem_fun<T,int,&T::g> |
否 | 否 | |
gf() |
global_fun<const T&,int,&T::gf> |
是 | 否 | |
gg() |
global_fun<T&,int,&T::gg> |
否 | 否 | |
reference_wrapper<T> |
i |
member<T,int,&T::i> |
是 | 是 |
j |
member<T,const int,&T::j> |
是 | 否 | |
f() |
const_mem_fun<T,int,&T::f> |
是 | 否 | |
g() |
mem_fun<T,int,&T::g> |
是 | 否 | |
gf() |
global_fun<const T&,int,&T::gf> |
是 | 否 | |
gg() |
global_fun<T&,int,&T::gg> |
是 | 否 | |
reference_wrapper<const T> |
i |
member<T,const int,&T::i> |
是 | 否 |
j |
member<T,const int,&T::j> |
是 | 否 | |
f() |
const_mem_fun<T,int,&T::f> |
是 | 否 | |
g() |
||||
gf() |
global_fun<const T&,int,&T::gf> |
是 | 否 | |
gg() |
||||
指向 T 的链式指针或指向 reference_wrapper<T> 的指针 |
i |
member<T,int,&T::i> |
是 | 是 |
j |
member<T,const int,&T::j> |
是 | 否 | |
f() |
const_mem_fun<T,int,&T::f> |
是 | 否 | |
g() |
mem_fun<T,int,&T::g> |
是 | 否 | |
gf() |
global_fun<const T&,int,&T::gf> |
是 | 否 | |
gg() |
global_fun<T&,int,&T::gg> |
是 | 否 | |
指向 const T 的链式指针或指向 reference_wrapper<const T> 的指针 |
i |
member<T,const int,&T::i> |
是 | 否 |
j |
member<T,const int,&T::j> |
是 | 否 | |
f() |
const_mem_fun<T,int,&T::f> |
是 | 否 | |
g() |
||||
gf() |
global_fun<const T&,int,&T::gf> |
是 | 否 | |
gg() |
“适用于 const
元素?”列指出了当传递 const 元素时(这与第一列中指定的元素有关,而不是引用的 T
对象),相应的键提取器是否可以使用。唯一出现否定值的情况是当元素是原始 T
对象时,对于 T::g
和 T:gg
,因为我们处理的是非 const 成员函数 (T::g
) 和接受 T
的非 const 引用的函数:这也意味着 T
元素的 multi_index_container
无法按 T::g
或 T::gg
进行排序,因为 multi_index_container
中包含的元素被视为常量。
“读/写?”列显示了哪些组合会产生 读/写键提取器。
在指定 member
键提取器时,需要小心维护 const
正确性:在某种意义上,const
限定符会被带到成员部分,即使该特定成员没有定义为 const
。例如,如果元素是 const T *
类型,则按 T::i
排序的指定方式不是 member<const T,int,&T::i>
,而是 member<T,const int,&T::i>
。
有关这些键提取器用法的实际演示,请参阅示例部分的 示例 2 和 示例 6。
修订日期:2020 年 4 月 19 日
© 版权所有 2003-2020 Joaquín M López Muñoz。根据 Boost 软件许可证 1.0 分发。(参见随附文件 LICENSE_1_0.txt 或访问 https://boost.ac.cn/LICENSE_1_0.txt 复制)