本周小贴士#144:关联容器中的异构查找

Posted -飞鹤-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了本周小贴士#144:关联容器中的异构查找相关的知识,希望对你有一定的参考价值。

作为TotW#144最初发表于2018年3月23日

由Samuel Benzaquen创作

介绍

关联容器使用一个关键字来关联一个元素。插入或从容器中查找元素需要等效键。 通常,容器要求键是特定类型的,这会导致调用处的效率低下,需要在接近等效的类型(如 std::string 和 absl::string_view)之间进行转换。

为了避免这种不必要的工作,一些容器提供了异构查找。 此功能允许调用者传递任何类型的键(只要用户指定的比较器函子支持它们)。 有关 STL 容器中此功能的示例,请参见 std::map::find。

透明函数子

透明函数子是一种接受不止一种特定类型的函数子。 它必须通过提供一个 is_transparent 内部类型来公开这个事实。 此内部类型的实际定义不相关,因为它仅用作标记。 通常使用将 is_transparent 设置为 void 的 using 声明。

当容器检测到透明函子时,它们的查找函数将原封不动地转发用户指定的值,而不是首先将其转换为 key_type(通过隐式或显式转换)。

隐式支持异构查找可能很危险,因为值之间的关系在转换后可能无法保持。 例如,1.0 < 1.1,但 static_cast(1.0) == static_cast(1.1)。 因此,使用 double 在 std::set 中查找值可能会导致不正确的结果。 这些潜在的错误是选择加入此功能的原因。

使用异构查找来提高性能

使用异构查找的一个常见原因是性能。 我们可以构造 key_type,但这样做需要我们宁愿避免的复杂的工作。 例如:

std::map<std::string, int> m = ...;
absl::string_view some_key = ...;
// 构造一个临时'std::string‘去执行查询
// 分配+拷贝+释放可能支配find()调用
auto it = m.find(std::string(some_key));

相反,我们可以像这样使用透明比较器:

struct StringCmp 
  using is_transparent = void;
  bool operator()(absl::string_view a, absl::string_view b) const 
    return a < b;
  
;

std::map<std::string, int, StringCmp> m = ...;
absl::string_view some_key = ...;
// 比较器’StringCmp'将接受任意能够隐式转换为'absl::string_view'的类型,并且
// 通过声明‘is_transparent'标签来说明。
// 我们能够传递'some_key'而不用先转换它为'std::string'来进行'find()'。
// 在这种情况下,这避免了需要构造‘std::string’实例的不必要内存分配。
auto it = m.find(some_key);

还有什么好处?

存在不可能或不方便创建有效的 key_type 对象只是为了进行查找的情况。 在这些情况下,我们可能希望使用更容易生成但包含查找所需信息的替代类型。 例如:

struct ThreadCmp 
  using is_transparent = void;
  // 规则重载
  bool operator()(const std::thread& a, const std::thread& b) const 
    return a.get_id() < b.get_id();
  
  // 透明重载
  bool operator()(const std::thread& a, std::thread::id b) const 
    return a.get_id() < b;
  
  bool operator()(std::thread::id a, const std::thread& b) const 
    return a < b.get_id();
  
  bool operator()(std::thread::id a, std::thread::id b) const 
    return a < b;
  
;

std::set<std::thread, ThreadCmp> threads = ...;
// 不能使用相同id来构建一个'std;:thread"实例,仅用来进行查询。
// 但是我们能够通过id来查找。
std::thread::id id = ...;
auto it = threads.find(id);

STL 容器支持和替代方案

有序容器 (std::map,set,multimap,multiset) 从 C++14 开始支持异构查找。 从 C++17 开始,无序容器仍然不支持它。 有添加此功能的建议,但尚未被接受。

然而,Swiss表的新系列支持对类字符串类型(std::string、absl::string_view 等)和智能指针(T*、std::shared_ptr、std::unique_ptr)的异构查找。 它们要求哈希函数和相等函数都被标记为透明的。 所有其他密钥类型都需要用户明确选择加入。

B-Tree 容器 (absl::btree_set,map,multiset,multimap) 也支持异构查找。

以上是关于本周小贴士#144:关联容器中的异构查找的主要内容,如果未能解决你的问题,请参考以下文章

本周小贴士#103:Flag是全局变量

本周小贴士#136:无序容器

本周小贴士#49:参数依赖查找

本周小贴士#86:带类的枚举

本周小贴士#116: Using声明和命名空间别名

本周小贴士#116: Using声明和命名空间别名