我啥时候应该使用 std::string / std::string_view 作为参数/返回类型

Posted

技术标签:

【中文标题】我啥时候应该使用 std::string / std::string_view 作为参数/返回类型【英文标题】:When should I use std::string / std::string_view for parameter / return type我什么时候应该使用 std::string / std::string_view 作为参数/返回类型 【发布时间】:2019-06-18 12:11:47 【问题描述】:

简介

我正在编写一些通信应用程序。在 C++17(没有 Boost)之前,我使用 std::string 并将其 const 引用作为 cls1

从 C++17 开始,我将 std::string_view 作为 cls2 引入我的代码。 但是,我没有明确的政策何时应该使用std::string_view。我的通信应用程序从网络接收数据并将其存储到recv_buffer。并从recv_buffer 创建一些应用程序类。

建筑

如果我只关注cls1 的构造函数,移动构造是有效的。但我认为参数s 来自哪里。如果它最初来自recv_buffer,我可以在接收(很早)点创建std::string_view。在启用recv_buffer 的生命周期期间,请在任何地方使用std::string_view。如果我需要存储recv_buffer 的部分然后创建std::string

我注意到的唯一例外是recv_buffer 始终包含我的应用程序类的完整数据。在这种情况下,移动构造是有效的。

吸气剂

我认为将返回类型用作std::string_view 具有优势。一些成员函数如substr() 是有效的。但到目前为止,我没有看到任何缺点。

问题

我怀疑我可能只看到std::string_view 的优点。在重写很多代码之前,我想知道你的想法。

PoC 代码

#include <string>

struct cls1 
    explicit cls1(std::string s):s_(std::move(s)) 
    std::string const& get() const  return s_; 
private:
    std::string s_;
;

struct cls2 
    explicit cls2(std::string_view s):s_(s) 
    std::string_view get() const  return s_; 
private:
    std::string s_;
;

#include <iostream>

int main() 
    // If all of the receive buffer is the target
    
        std::string recv_buffer = "ABC";
        cls1 c1(std::move(recv_buffer)); // move construct
        std::cout << c1.get().substr(1, 2) << std::endl; // create new string
    
    
        std::string recv_buffer = "ABC";
        cls2 c2(recv_buffer);            // copy happend
        std::cout << c2.get().substr(1, 2) << std::endl; // doesn't create new string
    

    // If a part of the receive buffer is the target
    
        std::string recv_buffer = "<<<ABC>>>";
        cls1 c1(recv_buffer.substr(3, 3)); // copy happend and move construct
        std::cout << c1.get().substr(1, 2) << std::endl; // create new string
    
    
        std::string recv_buffer = "<<<ABC>>>";
        std::string_view ref = recv_buffer;
        cls2 c2(ref.substr(3, 3)); // string create from the part of buffer directly
        std::cout << c2.get().substr(1, 2) << std::endl; // doesn't create new string
    

运行演示:https://wandbox.org/permlink/TW8w3je3q3D46cjk

【问题讨论】:

几天前有人问了非常相似的问题:***.com/questions/56601261/… 考虑 string_view 作为参考。如果它所指的对象消失了,那你就有问题了。 另外,std::string_view 缺少 std::string 拥有的所有修饰符方法(在使用 const ref 时你不关心),std::string::c_str() 也一样。 【参考方案1】:

std::string_view 是一种获取 some std::string const 成员函数的方法,如果您有一些 char* 或者您想引用字符串的子集,则无需创建 std::string。

将其视为 const 引用。如果它引用的对象由于任何原因消失(或更改),那么您就有问题了。如果您的代码可以返回引用,则可以返回 string_view。

例子:

#include <cstdio>
#include <string>
#include <vector>
#include <string.h>
#include <iostream>

int main()

    char* a = new char[10];
    strcpy(a,"Hello");
    std::string_view s(a);
    std::cout << s; // OK    
    delete[] a;
    std::cout << s;     // whops. UD. If it was std::string, no problem, it would have been a copy

More info.

编辑:它没有 c_str() 成员,因为这需要在子字符串的末尾创建一个 \0 ,而如果不修改就无法完成。

【讨论】:

谢谢!我了解一生。它类似于std::string const&amp;std::string_view。如果两者都可能,并且我不需要空终止,我选择std::string_view。我决定这是我的政策。一个例外是完整的std::string 总是可以作为构造函数的参数给出。在这种情况下,我使用std::stringstd::string&amp;&amp; 作为构造函数参数。 我想我只会在必须处理 char* 时使用字符串视图。如果我已经有一个 std::string,我只会在需要子字符串时才将它转移到 string_view。 确实如此。当我第一次询问时,我概括了我的问题,但实际的 recv_buffer 是 MQTT 数据包,例如 docs.oasis-open.org/mqtt/mqtt/v5.0/os/… 。在这种情况下,我认为基于 std::string_view 的方法更好地从 recv_bffer 获取字符串 a/bc/d 。正如您提到的,这是一种子字符串情况。【参考方案2】:

在以下情况下不返回字符串视图:

调用者需要一个以空字符结尾的字符串。处理 C API 时经常出现这种情况。 您不会将字符串本身存储在某个地方。在这种情况下,您确实将字符串存储在成员中。

请注意,字符串视图会因对原始字符串的操作(例如更改容量)以及原始字符串被破坏而失效。如果调用者需要字符串的时间长于存储字符串的对象的生命周期,那么他们可以从视图复制到自己的存储中。

【讨论】:

谢谢!我了解 std::string_view 的缺点。第1点是合理的。我知道第二点的意思是“不要返回本地临时对象的引用。”。在这种情况下,返回类型为std::stringstd::string_viewstd::string const&amp; 都不好。对吗? @TakatoshiKondo 返回字符串视图或对本地字符串的引用都不好,是的。

以上是关于我啥时候应该使用 std::string / std::string_view 作为参数/返回类型的主要内容,如果未能解决你的问题,请参考以下文章

我啥时候应该使用 QThread::HighestPriority

Firebase:我啥时候应该使用 refreshToken?

我啥时候应该使用助手? [关闭]

我啥时候应该使用“while 循环”?

我啥时候应该在 C 中使用 malloc,啥时候不应该?

我啥时候应该在 C 中使用 malloc,啥时候不应该?