看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第八篇~
本篇是系列译文的最后一篇(译文总数不到十来篇)~
C++11 中已经包含了8个关联容器,C++17 改进了这些容器的接口方法,现在你可以更加方便的向容器中插入元素,合并或者移动一个容器的元素至另一个"相似"容器中,并且新标准还统一了关联容器和顺序容器的访问方式.
在我深入讲解细节之前,让我先来回答一下之前的一个问题:什么是"相似"容器?
目前标准库包含8个关联容器:
所谓的相似容器,其实就是所含元素的数据结构相同并且 数据类型也相同的容器.std::set 和 std::multiset 的元素便拥有相同的数据结构, std::unordered_set 和 std::unordered_multiset, std::map 和 std::multimap, 以及 std::unordered_map 和 std::unordered_multimap, 这几个容器对包含的元素也拥有相同的数据结构.
当然,上面的说明还是比较简略的,更多的细节我在之前的文章中已经做过介绍,有兴趣的朋友可以看看.
现在让我们来看些全新的东西.
The improved interface of the associative containers
下面的代码示例较详尽的展示了改进的容器接口.
#include <iostream>
#include <map>
#include <string>
#include <utility>
using namespace std::literals; // 1
template <typename Cont>
void printContainer(const Cont& cont, const std::string& mess) { // 2
std::cout << mess;
for (const auto& pa : cont) {
std::cout << "(" << pa.first << ": " << pa.second << ") ";
}
std::cout << std::endl;
}
int main() {
std::map<int, std::string> ordMap{ {1, "a"s}, {2, "b"} }; // 3
ordMap.try_emplace(3, 3, 'C');
ordMap.try_emplace(3, 3, 'c');
printContainer(ordMap, "try_emplace: ");
std::cout << std::endl;
std::map<int, std::string> ordMap2{ {3, std::string(3, 'C')}, // 4
{4, std::string(3, 'D')} };
ordMap2.insert_or_assign(5, std::string(3, 'e'));
ordMap2.insert_or_assign(5, std::string(3, 'E'));
printContainer(ordMap2, "insert_or_assign: "); // 5
std::cout << std::endl;
ordMap.merge(ordMap2); // 6
std::cout << "ordMap.merge(ordMap2)" << std::endl;
printContainer(ordMap, " ordMap: ");
printContainer(ordMap2, " ordMap2: ");
std::cout << std::endl;
std::cout << "extract and insert: " << std::endl;
std::multimap<int, std::string> multiMap{ {2017, std::string(3, 'F')} };
auto nodeHandle = multiMap.extract(2017); // 7
nodeHandle.key() = 6;
ordMap.insert(std::move(nodeHandle));
printContainer(ordMap, " ordMap: ");
printContainer(multiMap, " multiMap: ");
return 0;
}
代码示例中我使用了 std::map, 因为多数情况下他都是我们使用关联容器的第一选择.另外,如果你需要存储大量元素并且保证访问效率,你就可以试试 std::unordered_map.在我之前的文章中,我对这两个容器的访问效率做了一些比较.
代码 (2) 处我编写了 printContainer 函数用来方便的输出关联容器的元素(可以附加一个消息标题),同样是为了方便,我在 (1) 处引入了命名空间 std::literals,这样我就可以使用 C++ string 中新的内建字面量(literal)了.代码 (3) 中定义的键值对 {1, "a"s} 便是 string 字面量的一个应用: "a"s 是 C++14 引入的 string 字面量定义方式,你只需要在 C 风格字符串后面添加一个 s 字符便可获得一个 C++ string(字面量).
现在,我要开始详细解释示例程序的代码了,为了理解方便,让我们先看下程序的输出:
新标准中增加了两种向关联容器中添加元素的方法: try_emplace 和 insert_or_assign.代码 (3) 处的 ordMap.try_emplace(3, 3, ‘C’) 尝试向 ordMap 添加一个元素,其中第一个参数 3 是元素的键, 后面的两个参数 3 和 ‘C’ 则直接用于调用元素值(这里是std::string)的构造函数.之所以这个方法以try为前缀命名,是因为如果对应的元素键已经存在,该方法便不会执行实际的添加操作.代码 (4) 处的 insert_or_assign 方法则与 try_emplace 不同,如果对应的元素键已经存在,他会将新的元素值赋值给已经存在的元素键(建立新的键值对映射).
C++17 中,你还可以合并关联容器.代码 (6) 处的 ordMap.merge(ordMap2) 将 ordMap2 合并入了 ordMap 中.这个过程的正式名称叫"拼接(splice)",以上面代码为例,拼接的过程就是从 ordMap2 中抽取(extract)每一个键值对并插入 ordMap 中,如果 ordMap 中已经存在相同的元素键,则不会执行插入操作.整个过程不会发生键值对的 copy 或者 move 操作,所以拼接之前指向键值对的指针(或者引用)仍然保持有效.你可以在相似的关联容器间执行合并操作,而所谓的相似容器,正如之前所说,就是容器所包含的元素拥有相同的数据结构和相同的数据类型.
代码 (7) 处继续进行容器的抽取和插入操作.新标准中的关联容器都有一个新的子类型:node_type,代码 (6) 中的容器合并操作内部就是通过使用 node_type 来完成的.你甚至可以使用 node_type 来改变一个键值对的键:代码 (7) 处的 auto nodeHandle multiMap.extract(2017) 从 std::multimap<int, std::string> 中抽取了键为 2017 的节点(node_type),接下来的代码 nodeHandle.key() = 6 将节点的键改为了 6, 然后使用 ordMap.insert(std::move(nodeHandle)) 将节点插入到了 ordMap 中,这里我必须使用 move 的方式来插入提取的节点,因为 node_type 并不支持拷贝.
当然,你也可以更改抽取节点的键后插入回同一个关联容器中(A),或者直接不做任何更改(B).除了更改键,你也可以更改节点的值©.
auto nodeHandle = multiMap.extract(2017); // A
nodeHandle.key() = 6;
multiMap.insert(std::move(nodeHandle));
auto nodeHandle = multiMap.extract(2017); // B
ordMap.insert(std::move(nodeHandle));
auto nodeHandle = multiMap.extract(2017); // C
nodeHandle.mapped() = std::string("ZZZ");
ordMap.insert(std::move(nodeHandle));
C++17 中引入了3个全局函数用以统一的访问容器.
Uniform container access
这3个函数分别是 std::size, std::empty, 和 std::data.
- std::size: 返回一个 STL 容器,或者一个 C++ string, 或者一个 C 数组的大小(size).
- std::empty: 返回一个 STL 容器,或者一个 C++ string, 或者一个 C 数组是否为空.
- std::data: 返回容器所包含元素的内存块指针.使用前提是容器必须支持 data() 方法(标准库中的 std::vector, std::string 和 std::array 支持该方法).