std::string_view:字符串类型的管道胶带

VisualStudio 2017包含对STD::String视图的支持,String视图是C++中添加的一种类型,用于服务以前由const char和const STD::String和参数所服务的一些角色。string_view既不是“better const std::string&”,也不是“better const char*”;它既不是它们的超集,也不是它们的子集。std::string_view是一种通用的“胶水”——一种描述读取字符串数据所需的最小公共接口的类型。它不要求数据以null结尾,并且对数据的生存期没有任何限制。这将为您提供“免费”的类型擦除,因为接受字符串视图的函数可以使其与任何类似字符串的类型一起工作,而无需将函数生成模板,也无需将该函数的接口约束到字符串类型的特定子集。

null
热释光;博士

字符串视图解决了参数的“每个平台和库都有自己的字符串类型”问题。它可以绑定到任何字符序列,因此您只需将函数编写为接受字符串视图:

void f(wstring_view); // string_view that uses wchar_t's

调用它,而不必关心调用代码使用的是什么样的stringlike类型(对于(char*,length)参数对,只需在其周围添加{})

// pass a std::wstring:std::wstring& s;         f(s);// pass a C-style null-terminated string (string_view is not null-terminated):wchar_t* ns = "";        f(ns);// pass a C-style character array of len characters (excluding null terminator):wchar_t* cs, size_t len; f({cs,len});// pass a WinRT stringwinrt::hstring hs;       f(hs);

f只是一个普通函数,它不一定是一个模板。

作为通用字符串参数的字符串视图

今天,用来传递字符串数据的最常见的“最小公分母”是以null结尾的字符串(或者标准称之为以null结尾的字符类型序列)。这与我们早在C++之前就已经有了,并且提供了干净的“C”互操作性。然而,char*及其支持库与可利用的代码相关联,因为长度信息是数据的带内属性,容易被篡改。此外,用于限定长度的空值禁止嵌入空值,并导致要求长度的最常见字符串操作之一在字符串长度上呈线性。

有时const std::string&可以用来传递字符串数据并删除源代码,因为它接受std::string对象、const char*指针和字符串文本(如“meow”)。不幸的是,const std::string&在与使用其他字符串类型的代码交互时会产生“阻抗不匹配”。如果你想和COM交谈,你需要使用BSTR。如果你想和温特谈谈,你需要一根绳子。用于NT、UNICODEu字符串等。每个编程域都有自己的新字符串类型、生存期语义和接口,但是很多文本处理代码并不关心这些。仅仅为了使不同的字符串类型满意而将数据的整个副本分配给处理,这对于性能和可靠性来说是次优的。

示例:接受std::wstring和winrt::hstring的函数

考虑以下程序。它有一个在单独的.cpp中编译的库函数,它不显式地处理所有字符串类型,但仍然可以处理任何字符串类型。

// library.cpp#include <stddef.h>#include <string_view>#include <algorithm>size_t count_letter_Rs(std::wstring_view sv) noexcept {    return std::count(sv.begin(), sv.end(), L'R');}
// program.cpp// compile with: cl /std:c++17 /EHsc /W4 /WX//    /I"%WindowsSdkDir%Include\%UCRTVersion%cppwinrt" .program.cpp .library.cpp#include <stddef.h>#include <string.h>#include <iostream>#include <stdexcept>#include <string>#include <string_view>#pragma comment(lib, "windowsapp")#include <winrt/base.h>// Library function, the .cpp caller doesn't need to know the implementationsize_t count_letter_Rs(std::wstring_view) noexcept;int main() {    std::wstring exampleWString(L"Hello wstring world!");    exampleWString.push_back(L' ');    exampleWString.append(L"ARRRR embedded nulls");    winrt::hstring exampleHString(L"Hello HSTRING world!");    // Performance and reliability is improved vs. passing std::wstring, as    // the following conversions don't allocate and can't fail:    static_assert(noexcept(std::wstring_view{exampleWString}));    static_assert(noexcept(std::wstring_view{exampleHString}));    std::wcout << L"Rs in " << exampleWString        << L": " << count_letter_Rs(exampleWString) << L"";    // note HStringWrapper->wstring_view implicit conversion when calling    // count_letter_Rs    std::wcout << L"Rs in " << std::wstring_view{exampleHString}        << L": " << count_letter_Rs(exampleHString) << L"";}

输出:

>.program.exeRs in Hello wstring world! ARRRR embedded nulls: 4Rs in Hello HSTRING world!: 1

前面的示例演示了stringu view(在本例中为wstringu view)的许多理想属性:

与制作计数字母的模板相比
由于只需要编译countu letter
的一个实例,因此减少了编译时间和代码大小。所使用的字符串类型的接口不必是统一的,只要向字符串类型添加合适的转换函数,就可以使用winrt::hstring、MFC CString或QString等类型。
vs.常量字符*
通过接受string视图,count letter不需要对输入进行strlen或wcslen。嵌入的空值工作没有问题,而且带内空值操作错误不可能引入错误。
vs.const std::字符串&
如上面的注释所述,string u视图避免了单独的分配和潜在的故障模式,因为它传递一个指向字符串数据的指针,而不是生成该数据的一个完整的拥有副本。
解析器的字符串视图

在解析应用程序时,另一个不分配非拥有的字符串片段(以string视图的形式公开)很有用的地方是。例如,C++ 17 STD::Fiels::Puffic实现是用Visual C++来分析和分解路径时在STD中使用:WSTRIGIGIVE视图。生成的字符串u视图可以直接从std::filesystem::path::filename()之类的函数返回,但是像std::filesystem::path::has u filename()之类的函数实际上不需要制作副本,因此很容易编写。

inline wstring_view parse_filename(const wstring_view text)	{	// attempt to parse text as a path and return the filename if it exists; otherwise,		// an empty view	const auto first = text.data();	const auto last = first + text.size();	const auto filename = find_filename(first, last); // algorithm defined elsewhere	return wstring_view(filename, last - filename);	}class path	{public:	// [...]	path filename() const		{	// parse the filename from *this and return a copy if present; otherwise,			// return the empty path		return parse_filename(native());		}	bool has_filename() const noexcept		{	// parse the filename from *this and return whether it exists		return !parse_filename(native()).empty();		}	// [...]	};

在string视图之前编写的std::experimental::filesystem实现中,path::filename()包含解析逻辑,并返回std::experimental::filesystem::path。hasu filename是根据filename实现的, 如标准所述 ,分配一条路径以立即将其丢弃。

迭代器调试支持

在调试构建时,MSVC的string u视图实现被检测到多种缓冲区管理错误。有效的输入范围在构造string u视图的迭代器时被标记到迭代器中,不安全的迭代器操作被一条描述问题所在的消息阻塞。

// compile with cl /EHsc /W4 /WX /std:c++17 /MDd .program.cpp#include <crtdbg.h>#include <string_view>int main() {    // The next 3 lines cause assertion failures to go to stdout instead of popping a dialog:    _set_abort_behavior(0, _WRITE_ABORT_MSG);    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);    // Do something bad with a string_view iterator:    std::string_view test_me("hello world");    (void)(test_me.begin() + 100); // dies}
>cl /nologo /MDd /EHsc /W4 /WX /std:c++17 .	est.cpptest.cpp>.	est.exexstring(439) : Assertion failed: cannot seek string_view iterator after end

现在,这个例子可能看起来有点明显,因为我们明显地增加了迭代器,使其超出了输入所允许的范围,但是捕捉到这样的错误可以使调试更为复杂。例如,函数希望将迭代器移动到下一个“)”:

// compile with cl /EHsc /W4 /WX /std:c++17 /MDd .program.cpp#include <crtdbg.h>#include <string_view>using std::string_view;string_view::iterator find_end_paren(string_view::iterator it) noexcept {    while (*it != ')') {        ++it;    }    return it;}int main() {    _set_abort_behavior(0, _WRITE_ABORT_MSG);    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);    string_view example{"malformed input"};    const auto result = find_end_paren(example.begin());    (void)result;}
>cl /nologo /EHsc /W4 /WX /std:c++17 /MDd .program.cppprogram.cpp>.program.exexstring(358) : Assertion failed: cannot dereference end string_view iterator
陷阱#1:std::stringŠu视图不拥有其数据,或延长生存期

因为stringu视图没有它的实际缓冲区,所以很容易编写假设数据会存在很长时间的代码。演示此问题的一个简单方法是使用一个字符串视图数据成员。例如,以下结构是危险的:

struct X {    std::string_view sv; // Danger!    explicit X(std::string_view sv_) : sv(sv_) {}};because a caller can expect to do something like:int main() {    std::string hello{"hello"};    X example{hello + " world"}; // forms string_view to string destroyed at the semicolon    putc(example.sv[0]); // undefined behavior}

在本例中,表达式“hello+”world“`创建了一个临时std::string,在调用X的构造函数之前,它被转换为std::string视图。X将一个stringu视图存储到该临时字符串,并且该临时字符串在构造“example”的完整表达式的末尾被销毁。在这一点上,如果X试图存储一个被释放的const char*,就没有什么不同了。X确实想在这里延长字符串数据的生存期,所以它必须制作一个实际的副本。

当然,在某些情况下,stringu视图成员是好的;如果您正在实现一个解析器,并且正在描述一个与输入相关的数据结构,这可能是可以的,就像std::regex对std::subu match所做的那样。请注意,string u视图的生存期语义更像指针。

陷阱2:类型推导和隐式转换

尝试通过接受基本的字符串视图而不是字符串视图或wstring视图来将函数泛化为不同的字符类型,会阻止隐式转换的预期用途。如果我们从前面修改程序以接受模板而不是wstringu视图,那么这个示例就不再有效了。

// program.cpp// compile with: cl /std:c++17 /EHsc /W4 /WX//    /I"%WindowsSdkDir%Include\%UCRTVersion%cppwinrt" .program.cpp#include <stddef.h>#include <string.h>#include <algorithm>#include <iostream>#include <locale>#include <stdexcept>#include <string>#include <string_view>#pragma comment(lib, "windowsapp")#include <winrt/base.h>template<class Char>size_t count_letter_Rs(std::basic_string_view<Char> sv) noexcept {    return std::count(sv.begin(), sv.end(),        std::use_facet<std::ctype<Char>>(std::locale()).widen('R'));}int main() {    std::wstring exampleWString(L"Hello wstring world!");    winrt::hstring exampleHString(L"Hello HSTRING world!");    count_letter_Rs(exampleWString); // no longer compiles; can't deduce Char    count_letter_Rs(std::wstring_view{exampleWString}); // OK    count_letter_Rs(exampleHString); // also no longer compiles; can't deduce Char    count_letter_Rs(std::wstring_view{exampleHString}); // OK}

在本例中,我们希望exampleWString隐式转换为基本的字符串视图。然而,为了实现这一点,我们需要模板参数演绎来演绎CharT==wchar ,这样我们就得到了countu letter
。模板参数演绎在重载解析或试图查找转换序列之前运行,因此根本不知道basicu string与basicu string视图相关,类型演绎失败,程序不编译。因此,在您的接口中,您更愿意接受像stringu view或wstringu view这样的基本u stringu视图的专门化,而不是模板化的基本u stringu视图。

最后

我们希望StrugIVIEW视为一个互操作性的桥梁,允许更多的C++代码无缝通信。我们总是对你的反馈感兴趣。如果您遇到问题,请让我们知道通过 帮助>报告产品中的问题 ,或通过 开发者社区 . 把你的建议告诉我们 用户语音 . 你也可以在Twitter上找到我们( @视觉 )还有Facebook( msftvisualcpp软件 ).

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享