[译]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 的原因.

猜你喜欢

转载自blog.csdn.net/tkokof1/article/details/81808647