2

[译]C++17,标准库有哪些新变化?

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/81808647
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++17 有许多新的标准库变化,简单起见,这篇文章只介绍了以下内容:std::string_view,标准模板库中新添加的并行算法,新的文件系统库,以及3个新的数据类型:std::any, std::optional, 和 std::variant.让我们来了解一下其中的细节.

首先看看 std::string_view.

std::string_view

std::string_view 代表一个字符串的非所有权引用(即不负责管理引用字符串的生命周期),他表示的是一个字符序列(可以是 C++ 中的 string 或者 C风格的字符串)的"视图".C++17 中为不同的字符类型提供了四种 string_view :

std::string_view      std::basic_string_view<char>
std::wstring_view     std::basic_string_view<wchar_t>
std::u16string_view   std::basic_string_view<char16_t>
std::u32string_view   std::basic_string_view<char32_t>

你也许会有疑问:为什么我们需要 std::string_view 呢(Google, LLVM 和 Bloomberg 甚至实现了自己的 string_view 版本)? .答案其实很简单: 因为 std::string_view 可以高效的进行复制! 而高效的原因在于 std::string_view 的创建成本很低, 仅需要两个数据:字符序列的指针以及字符序列的长度. std::string_view 以及他的3个"兄弟"类型(指 std::wstring_view, std::u16string_view 和 std::u32string_view)提供了和 std::string 一致的字符串读取接口,另外也新增了两个方法:remove_prefix 和 remove_suffix.(译注:译文对作者的原始示例代码做了些许调整,原始代码请参看原文)

#include <iostream>
#include <string>
#include <string_view>

int main()
{
    std::string str = "   A lot of space";
	std::string_view strView = str;
	strView.remove_prefix(std::min(strView.find_first_not_of(" "), strView.size()));
	std::cout << "str      :  " << str << std::endl
		      << "strView  : " << strView << std::endl;

	std::cout << std::endl;

	char arr[] = { 'A', ' ', 'l', 'o', 't', ' ', 'o', 'f', ' ', 's', 'p', 'a', 'c', 'e', '\0', '\0', '\0' };
	std::string_view strView2(arr, sizeof arr);
	auto trimPos = strView2.find('\0');
	if (trimPos != strView2.npos) strView2.remove_suffix(strView2.size() - trimPos);
	std::cout << "arr     : " << arr << ", size=" << sizeof arr << std::endl
		      << "strView2: " << strView2 << ", size=" << strView2.size() << std::endl;
		      
    return 0;
}

示例代码应该没有什么令人惊讶的地方:第8行代码创建了引用 C++ string 的 std::string_view(strView变量), 而第16行代码中创建的 std::string_view(strView2变量) 引用的则是字符数组.在第9行代码中,我们通过组合使用 remove_prefix 和 find_first_not_of 方法移除了 strView 的所有前导空格符,同样在第21行代码中, 借助 remove_suffix 方法, strView2 的所有尾随"\0"符号也被移除了.

image

下面介绍的内容你应该更加熟悉.

Parallel algorithm of the Standard Template Library(标准模板库中的并行算法)

关于STL中并行算法的介绍比较简短: 标准库中的 69 个算法会提供串行,并行以及矢量并行这3个版本.另外,新标准(C++17)也引入了 8 个(此处有误,见后面译注)新算法.下面的示意图标明了所有相关算法的名字,其中新引入的算法标为红色,非新引入的算法则为黑色.(译注:图中红色标明的 for_each 并非是新算法,所以实际C++17新引入的算法只有7个)

image

算法的介绍这么多了,关于这个话题的进一步细节你可以看看我写的另外一篇文章.

相比较算法,文件系统库应该属于全新的内容.

The filesystem library

新的文件系统库基于 boost::filesystem,并且文件系统库中的一些组件是可选的,这意味着并不是每一个文件系统库实现都支持标准定义的所有功能.例如, FAT-32 文件系统便不支持符号链接.

文件系统库基于3个概念: 文件(file), 文件名(file name) 以及 文件路径(path). file 可以是目录,硬链接,符号链接或者常规文件.path 则可以是绝对路径或者相对路径.

filesystem 提供了强大的读取及操作文件的接口,
你可以在cppreference.com上获取到更多细节,下面的示例代码可以给你一些初步印象:

#include <fstream>
#include <iostream>
#include <string>
#include <filesystem>
namespace fs = std::filesystem;

int main()
{
	std::cout << "Current path: " << fs::current_path() << std::endl;

	std::string dir = "sandbox/a/b";
	fs::create_directories(dir);

	std::ofstream("sandbox/file1.txt");
	fs::path symPath = fs::current_path() /= "sandbox";
	symPath /= "syma";
	fs::create_symlink("a", symPath);

	std::cout << "fs::is_directory(dir): " << fs::is_directory(dir) << std::endl;
	std::cout << "fs::exists(symPath): " << fs::exists(symPath) << std::endl;
	std::cout << "fs::symlink(symPath): " << fs::is_symlink(symPath) << std::endl;

	for (auto& p : fs::recursive_directory_iterator("sandbox"))
	{
		std::cout << p.path() << std::endl;
	}
	fs::remove_all("sandbox");
	
	return 0;
}

第9行代码中的 fs::current_path() 方法可以返回当前工作目录.你也可以使用
fs::create_directories 方法(代码第12行)创建层级目录. fs::path 重载了 /= 操作符,借助他我们可以方便的创建符号链接(第17行),你也可以使用文件库提供的接口来检查文件的各项属性(19行到21行).23行的 fs::recursive_directory_iterator 功能非常强大,你可以使用他来递归的遍历某个目录,当然,你也可以使用 fs::remove_all 来删除某个目录(第27行).

代码的输出如下:

image

新加入的数据类型 std::any, std::optional, 和 std::variant 都基于 boost程序库.

std::any

如果你想创建一个可以包含任意类型元素的容器,那么你就应该使用std::any,不过确切来说的话,std::any 并不是对任意类型都提供存储支持,只有可复制的类型才能存放入 std::any.下面列一段简短的示例代码:

#include <iostream>
#include <string>
#include <vector>
#include <any>

struct MyClass {};

int main() 
{
	std::cout << std::boolalpha;

	std::vector<std::any> anyVec { true, 2017, std::string("test"), 3.14, MyClass() };
	std::cout << "std::any_cast<bool>anyVec[0]: " << std::any_cast<bool>(anyVec[0]) << std::endl; // true
	int myInt = std::any_cast<int>(anyVec[1]);
	std::cout << "myInt: " << myInt << std::endl;                                    // 2017

	std::cout << std::endl;
	std::cout << "anyVec[0].type().name(): " << anyVec[0].type().name() << std::endl;             // b
	std::cout << "anyVec[1].type().name(): " << anyVec[1].type().name() << std::endl;             // i
	
	return 0;
}

示例代码的输出已经在注释中写明了.代码第 12 行创建了一个 std::vectorstd::any,你必须使用 std::any_cast 来获取其中的元素,如果你向 std::any_cast 传递了错误的数据类型,那么就会产生转型异常(std::bad_any_cast).你可以去cppreferenc.com获取更多相关细节或者等待我之后的更多文章介绍.

std::any 可以存储任意类型(译注:这里的任意类型指可复制的类型)的数据,而 std::optional 则支持存储数据或者不存储数据.

std::optional

std::optional 这里就不做介绍了,在之前我写的 Monads in C++ 中就已经介绍了这个单子(指std::optional).(译注: 单子(Monad) 是函数式编程编程的概念,简单理解的话可以看看这里)

我们再来看下 std::variant.

std::variant

std::variant 是一个类型安全的联合体(union).一个 std::variant 实例存储着其指定类型中某一类型的数据,并且 std::variant 的指定类型不能是引用类型,数组类型以及 void 类型,不过 std::variant 可以指定重复的数据类型(譬如指定多个int). std::variant 默认会以其第一个指定类型进行初始化,这就要求该类型(第一个指定类型)必须支持默认构造函数,下面是一个基于cppreference.com的代码示例:

#include <variant>
#include <string>

int main() 
{
	std::variant<int, float> v, w;
	v = 12;                              // v contains int
	int i = std::get<int>(v);
	w = std::get<int>(v);
	w = std::get<0>(v);                  // same effect as the previous line
	w = v;                               // same effect as the previous line

	//std::get<double>(v);               // error: no double in [int, float]
	//std::get<3>(v);                    // error: valid index values are 0 and 1

	try 
	{
		float f = std::get<float>(w);    // w contains int, not float: will throw
	}
	catch (std::bad_variant_access&) 
	{
	}

	std::variant<std::string> v2("abc"); // converting constructors work when unambiguous
	v2 = "def";                          // converting assignment also works when unambiguous
	
	return 0;
}

第6行代码中我创建了两个 std::variants 实例 v 和 w,他们的指定类型为 int 和 float,并且初始值为0(第一个指定类型 int 的默认初始值).第7行代码中我将整型12赋值给了v,后面我们可以通过 std::get(v) 来获取该值.第9行到11行代码中,我使用了3种方式将v中的数值赋值给了w. std::variants 的使用自然也有一定的规则限制,你可以使用指定某一类型(第9行代码)或者指定某一索引(第10行代码)的方式来获取 std::variants 的数值,但是指定的类型必须是唯一的,指定的索引也必须是有效的.第18行代码中我尝试从 w 中获取 float 类型数据,但是由于 w 目前包含 int 类型数据,所以会产生 std::bad_variant_access 异常.另外值得一提的是, std::variants 的构造函数以及赋值函数支持类型转换(要求转换没有歧义),这也是第24行及25行代码中我可以使用C风格的字符串直接初始化(或者赋值) std::variantstd::string 的原因.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK