4

[译]C++17,容器的持续改进与统一访问

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/82776945
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[译]C++17,容器的持续改进与统一访问

看到一个介绍 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 支持该方法).

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK