1. 可优先使用std::string_view作为函数入参
如下的代码,当只提供入参是std::string的版本时,当传入const char*时会构造一个std::string对象。
#include <iostream>
#include <format>
#include <string>
#include <string_view>
#include <iomanip>
void print(const std::string& str){
std::cout << "**version1**, " << __func__ << ", " << str << std::endl;
}
/*void print(const char* str){
std::cout << "--version2--, " << __func__ << ", " << str << std::endl;
}
void print(const std::string_view str){
std::cout << "==version3==, " << __func__ << ", " << str << std::endl;
}*/
auto main()->int{
const char* ch = "const char*";
std::string str("std::string");
print(ch); // **version1**, print, const char*
print(str); // **version1**, print, std::string
}
可以提供重载的print来避免上述问题,不过会增加代码量。
#include <iostream>
#include <format>
#include <string>
#include <string_view>
#include <iomanip>
void print(const std::string& str){
std::cout << "**version1**, " << __func__ << ", " << str << std::endl;
}
void print(const char* str){
std::cout << "--version2--, " << __func__ << ", " << str << std::endl;
}
/*void print(const std::string_view str){
std::cout << "==version3==, " << __func__ << ", " << str << std::endl;
}*/
auto main()->int{
const char* ch = "const char*";
std::string str("std::string");
print(ch); // --version2--, print, const char*
print(str); // **version1**, print, std::string
}
而使用std::string_view作为入参可以同时兼容入参是std::string和const char*的情况。
#include <iostream>
#include <format>
#include <string>
#include <string_view>
#include <iomanip>
/*void print(const std::string& str){
std::cout << "**version1**, " << __func__ << ", " << str << std::endl;
}
void print(const char* str){
std::cout << "--version2--, " << __func__ << ", " << str << std::endl;
}*/
void print(const std::string_view str){
std::cout << "==version3==, " << __func__ << ", " << str << std::endl;
}
auto main()->int{
const char* ch = "const char*";
std::string str("std::string");
print(ch); // ==version3==, print, const char*
print(str); // ==version3==, print, std::string
}
但是当三个版本的print都提供时编译器还是会进行最佳匹配。
#include <iostream>
#include <format>
#include <string>
#include <string_view>
#include <iomanip>
void print(const std::string& str){
std::cout << "**version1**, " << __func__ << ", " << str << std::endl;
}
void print(const char* str){
std::cout << "--version2--, " << __func__ << ", " << str << std::endl;
}
void print(const std::string_view str){
std::cout << "==version3==, " << __func__ << ", " << str << std::endl;
}
auto main()->int{
const char* ch = "const char*";
std::string str("std::string");
print(ch); // --version2--, print, const char*
print(str); // **version1**, print, std::string
}
2. std::string_view使用示例
2.1 示例一
#include <iostream>
#include <string>
#include <string_view>
std::string generateGreeting(std::string_view name){
std::string result("Hello ");
result += name;
result.append("!");
return result;
}
auto main()->int{
//using namespace std::literals;
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
std::cout << generateGreeting("World") << std::endl; // 实参是const char*
std::cout << generateGreeting("World"s) << std::endl; // 实参是std::string(通过const char*及std::string operator""s构造)
std::cout << generateGreeting("World"sv) << std::endl; // 实参是std::string_view(通过const char*及constexpr std::string_view operator""sv构造)
}
输出:
Hello World!
Hello World!
Hello World!
2.2 示例二
#include <iostream>
#include <string>
#include <string_view>
auto main()->int{
const char* name = "TARANIS";
std::cout << name << std::endl; // TARANIS
std::cout << (void*)name << std::endl; // 0x402011
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
std::cout << (void*)("Hello World!"sv.data()) << std::endl; // 0x402004
}
2.3 示例三
#include <iostream>
#include <string>
#include <string_view>
#include <format>
auto main()->int{
using namespace std::literals::string_view_literals;
std::string_view fruit("banana"sv);
size_t origin_size = fruit.size();
std::cout << std::format("{0:>{1}}", fruit, origin_size) << "'s address = " << (void*)fruit.data() << std::endl;
fruit.remove_prefix(1);
std::cout << std::format("{0:>{1}}", fruit, origin_size) << "'s address = " << (void*)fruit.data() << std::endl;
fruit.remove_prefix(1);
std::cout << std::format("{0:>{1}}", fruit, origin_size) << "'s address = " << (void*)fruit.data() << std::endl;
fruit.remove_prefix(1);
std::cout << std::format("{0:>{1}}", fruit, origin_size) << "'s address = " << (void*)fruit.data() << std::endl;
}
上面这段代码的输出可能是:
banana's address = 0x403019
anana's address = 0x40301a
nana's address = 0x40301b
ana's address = 0x40301c
由输出的地址可知视图fruit观察👀的对象并没有变,只是起始地址逐渐加1。
2.4 示例四
std::string_view与std::string对比。
2.4.1 std::string_view的示例
#include <iostream>
#include <string>
#include <string_view>
#include <format>
auto main()->int{
using namespace std::literals::string_view_literals;
std::string_view fruit("banana"sv);
size_t origin_size = fruit.size();
std::cout << std::format("{0:>{1}}", fruit, origin_size) << "'s address = " << (void*)fruit.data() << std::endl;
auto sub = fruit.substr(2,4);
std::cout << std::format("{0:>{1}}", sub, origin_size) << "'s address = " << (void*)sub.data() << std::endl;
}
上面这段代码的输出可能是:
banana's address = 0x4151d2
nana's address = 0x4151d4
由输出的地址可知视图fruit观察👀的对象并没有变,只是起始地址加了2(因为substr是从2开始的)。
2.4.1 std::string的示例
#include <iostream>
#include <string>
#include <format>
auto main()->int{
using namespace std::literals::string_literals;
std::string fruit("banana"s);
size_t origin_size = fruit.size();
std::cout << std::format("{0:>{1}}", fruit, origin_size) << "'s address = " << (void*)fruit.data() << std::endl;
auto sub = fruit.substr(1);
std::cout << std::format("{0:>{1}}", sub, origin_size) << "'s address = " << (void*)sub.data() << std::endl;
}
上面这段代码的输出可能是:
banana's address = 0x7fff682b5650
anana's address = 0x7fff682b5620
输出的地址差异较大,所以对于std::string来说调用substr是构造了一个新的std::string。
2.5 不可通过下标的方式修改std::string_view
#include <iostream>
#include <string_view>
#include <string>
int main(){
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
std::string_view view = "Hello World."sv;
std::string str = "Hello World."s;
str[2] = '5'; // OK
//view[2] = '5'; // 编译报错,error: assignment of read-only location 'view.std::basic_string_view<char>::operator[](2)'
}
3. 务必注意std::string_view并不拥有数据
std::string_view并不拥有数据,因此使用std::string_view期间一定要注意其引用的对象是否有效。
3.1 错误示例一
// g++ main.cpp -std=c++20 -fsanitize=address
#include <iostream>
#include <string>
#include <string_view>
auto main()->int{
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
std::string_view str_v;
{
std::string str("abcdefg"s);
str_v = str;
} // 此处str已经析构了,使用str_v属于未定义行为了
std::cout << str_v << std::endl;
}
上述代码编译后,运行报错如下:
=================================================================
==1==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7409c2d00050 at pc 0x7409c52bb971 bp 0x7ffeff503c60 sp 0x7ffeff503420
READ of size 7 at 0x7409c2d00050 thread T0
#0 0x7409c52bb970 (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0x6b970) (BuildId: 53df075b42b04e0fd573977feeb6ac6e330cfaaa)
#1 0x7409c5132d8c in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) (/opt/compiler-explorer/gcc-13.2.0/lib64/libstdc++.so.6+0x144d8c) (BuildId: 925fdbb642c48958ee86b4c067db1fd9baa5e4d8)
#2 0x403533 in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::basic_string_view<char, std::char_traits<char> >) /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/string_view:762
#3 0x4024b6 in main /app/example.cpp:31
#4 0x7409c4c29d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 962015aa9d133c6cbcfb31ec300596d7f44d3348)
#5 0x7409c4c29e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 962015aa9d133c6cbcfb31ec300596d7f44d3348)
#6 0x402214 in _start (/app/output.s+0x402214) (BuildId: 4e4307113b24ee741da51d08913a90cf1c20cea4)
Address 0x7409c2d00050 is located in stack of thread T0 at offset 80 in frame
#0 0x4022e5 in main /app/example.cpp:22
This frame has 2 object(s):
[32, 48) 'str_v' (line 26)
[64, 96) 'str' (line 28) <== Memory access at offset 80 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope (/opt/compiler-explorer/gcc-13.2.0/lib64/libasan.so.8+0x6b970) (BuildId: 53df075b42b04e0fd573977feeb6ac6e330cfaaa)
Shadow bytes around the buggy address:
0x7409c2cffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7409c2cffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7409c2cffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7409c2cfff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7409c2cfff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7409c2d00000: f1 f1 f1 f1 00 00 f2 f2 f8 f8[f8]f8 f3 f3 f3 f3
0x7409c2d00080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7409c2d00100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7409c2d00180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7409c2d00200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7409c2d00280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==1==ABORTING
3.2 错误示例二
#include <iostream>
#include <string>
#include <string_view>
auto main()->int{
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
std::string_view str_v = "Hello World."; // 没问题
//std::string_view str_v = "Hello World."sv; // 没问题
/* 下面两句在给std::string_view赋值之后std::string临时对象就被析构了,再使用就是未定义行为 */
//std::string_view str_v = std::string("Hello World."); // ERROR: AddressSanitizer: stack-use-after-scope
//std::string_view str_v = "Hello World."s; // ERROR: AddressSanitizer: stack-use-after-scope
std::cout << str_v << std::endl;
}
文章介绍了如何在C++中优先使用std::string_view作为函数参数以提高代码效率和兼容性,避免了因传入不同类型的参数(如constchar*和std::string)而可能产生的额外对象构造。同时强调了std::string_view的内存安全特性及其与std::string的不同之处,以及在实际示例中的应用和注意事项。
3170

被折叠的 条评论
为什么被折叠?



