C++17标准库中新增了一个类型,std::string_view。这个类型可以看成是指向字符串的指针加上该字符串的长度的集合。和std::string相比,使用string_view可以避免一些内存分配,从而提高程序性能。但是有一点需要注意:string_view并不拥有其指针所指向的字符串,当该字符串被释放的时候,string_view有效性也就随着消失了。
string_view本身是只读的,相当于const std::string&
,但是string_view中的指针以及字符长度可以随意修改(在原字符串的有效范围之内),而不会引起内存分配,同时也保证不会访问字符串有效范围之外的部分,导致内存访问错误。
一个简单的例子
#include <string_view>
#include <string>
#include <iostream>
using namespace std;
void f(string_view sv)
{
cout << sv << endl;
}
int main()
{
f("hello");
return 0;
}
编译上面的代码并执行:
> c++ -std=c++17 z.cc -o z
> ./z
hello
std::string可以自动转化为std::string_view,修改main函数如下:
int main()
{
string str = "hello";
f(str);
return 0;
}
编译并执行上述代码所得到的输出依然是:hello
。
假设我们有一个struct可以从一个string_view构造出来:
struct Foo
{
Foo(std::string_view sv)
: size(sv.size())
{}
size_t size;
};
修改main函数如下:
int main()
{
string str = "hello";
Foo foo(str);
cout << str << " " << foo.size << endl;
return 0;
}
编译执行后,输出结果:hello 5
。
但是我们做些许修改,把Foo foo(str);
替换成Foo foo = str;
:
int main()
{
string str = "hello";
Foo foo = str;
cout << str << " " << foo.size << endl;
return 0;
}
会发现居然编译出错。看来在C++中虽然这两种方式都是用来构造Foo,但是语义还是有所不同的。在Foo foo = str;
这种形势下,编译器不会进行自动隐式转换。
具体出错信息如下:
z.cc:24:7: error: no viable conversion from 'std::__1::string' (aka
'basic_string<char, char_traits<char>, allocator<char> >') to 'Foo'
Foo foo = str;
^ ~~~
z.cc:12:8: note: candidate constructor (the implicit copy constructor) not
viable: no known conversion from 'std::__1::string' (aka
'basic_string<char, char_traits<char>, allocator<char> >') to
'const Foo &' for 1st argument
struct Foo
^
z.cc:12:8: note: candidate constructor (the implicit move constructor) not
viable: no known conversion from 'std::__1::string' (aka
'basic_string<char, char_traits<char>, allocator<char> >') to 'Foo &&'
for 1st argument
struct Foo
^
z.cc:14:3: note: candidate constructor not viable: no known conversion from
'std::__1::string' (aka 'basic_string<char, char_traits<char>,
allocator<char> >') to 'std::string_view' (aka
'basic_string_view<char>') for 1st argument
Foo(std::string_view sv)
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchai
n/usr/include/c++/v1/string:869:5: note:
candidate function
operator __self_view() const _NOEXCEPT { return __self_view(data(...
^
1 error generated.
我们可以使用<type_traits>
中定义的的std::is_convertible
模板类来判断类型之间是否能够直接转化:
int main()
{
string str = "hello";
// Foo foo = str;
// cout << str << " " << foo.size << endl;
cout << is_convertible<string, string_view>::value << endl;
cout << is_convertible<string_view, Foo>::value << endl;
cout << is_convertible<string, Foo>::value << endl;
return 0;
}
上面程序的编译输出结果是:
1
1
0
说明string
到Foo
之间不存在直接的转化关系。但是string_view
是这两个类型的中间类型,是否有办法通过这个中间类型来搭桥呢?答案是肯定的,但是需要借助下面这个类型助手:
- std::enable_if
增加一个Foo的模版构造函数:
template<class T, typename enable_if<is_convertible<T, string_view>::value, int>::type = 0>
Foo(const T& t)
: Foo(string_view(t))
{}
简单的解释一下上面这个模板函数,它只在T的类型可以转化为string_view的时候才生效。然后Foo(const T& t)
通过Foo(string_view(t))
把参数转化为string_view,所以实际调用的是Foo(string_view)
这个构造函数。
再编译之前的main函数,输出就变成三个1了。
把main函数改为:
int main()
{
string str = "hello";
Foo foo = str;
cout << str << " " << foo.size << endl;
return 0;
}
编译并执行,可以看到Foo foo = str;
编译通过了,输出结果为:hello 5
。
参考
(完)
05-12更新
在上面的例子中,也可以使用 Foo foo = {str};
这种方式而不需要增加Foo的模版构造函数。
在C++11之前,Foo foo = {str};
属于copy initialization,对隐式转化的限制比较严格,看下面的例子:
struct S { S(std::string) {} }; // implicitly convertible from std::string
S s("abc"); // OK: conversion from const char[4] to std::string
S s = "abc"; // Error: no conversion from const char[4] to S
S s = "abc"s; // OK: conversion from std::string to S
在C++11以及之后,Foo foo = {str};
属于list initialization,对隐式转化有较高的宽容度,所以不会报错。
(更新完)