std::string_view

文章介绍了如何在C++中优先使用std::string_view作为函数参数以提高代码效率和兼容性,避免了因传入不同类型的参数(如constchar*和std::string)而可能产生的额外对象构造。同时强调了std::string_view的内存安全特性及其与std::string的不同之处,以及在实际示例中的应用和注意事项。

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_viewstd::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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值