【问题标题】:Return value optimization for string_view inside shared_ptrshared_ptr 内 string_view 的返回值优化
【发布时间】:2021-06-17 03:52:07
【问题描述】:

很难用语言表达,所以直接跳一段半伪代码。

我有一个下载函数 (http GET),它在我的主代码中被多次调用。

std::string download_data(){
    std::shared_ptr<HttpResponse> response = some_http_client->send_request("some_link");
    return std::string(response->body()); // response->body() is a std::string_view.
}

我正在使用的http_client,返回一个shared_ptr作为响应,这个响应(我排除了HTTP错误处理的代码,假设它是200。),包含一个response-&gt;body(),它是一个@987654325 @。

此代码运行良好,但是,我想确保每次调用/返回此函数时都不会复制下载的数据。

我的主要问题:

  • 我使用的当前代码是否有返回值优化? (有什么需要做的吗?)
  • 如果没有,我可以直接返回return response-&gt;body(); 吗?函数返回后,shared_ptr 中的 string_view 是否有效?

我考虑过或在旧版本代码中使用的东西:

  • 返回 std::string(另一个 http 客户端返回 std::string 作为正文)。
  • 返回std::move
  • 不用写函数,只要把函数体调用这个函数的所有地方都替换掉,直接使用response-&gt;body,避免返回(我讨厌它)。

这样做的正确方法是什么?

我的工具链:

Ubuntu 20.04 (GLIBC 2.31)、g++ 10.2C++20

【问题讨论】:

    标签: c++ optimization object-lifetime return-value-optimization


    【解决方案1】:

    您的代码将使用 RVO。它返回一个与函数返回类型相同的临时值,这是cases 之一,其中 RVO 是必需的。

    当然,它仍然需要一份数据副本,作为string 构造函数的一部分,该构造函数接受string_view 作为参数。

    您不能只通过 string_view 自己。它只不过是一对指向别人数据的指针。根据您的代码,这几乎可以肯定是 response 拥有的数据,在您可以使用返回的 string_view 之前,这些数据就会过期。

    你基本上有两个选择。您可以复制数据,也可以保留它。您当前的代码将其复制一次(感谢 RVO),因此对于这种情况,它是我们所能获得的理想选择。但是,还有另一种方法。我们可以返回一个指向字符串视图的“别名”共享指针。让您的函数返回一个std::shared_ptr&lt;std::string_view&gt;,我们将进行设置以使其正常工作。

    shared_ptr&lt;T&gt; 的别名构造函数如下所示:

    template <typename Y>
    shared_ptr(const shared_ptr<Y>& custodian, T* ward)
    

    它创建一个shared_ptr,当它被取消引用时,它指向病房。但是,它“拥有”托管人,托管人可以是任何其他类型。在这个共享指针被销毁之前,保管人不会被销毁。

    要使用它,我们必须创建一个新类,它包含一个 shared_ptr&lt;HttpResponse&gt; 和一个引用响应中数据的主体 string_view。我将其命名为BodyCustodian,以使命名尽可能一致。

    struct BodyCustodian
    {
        BodyCustodian(const std::shared_ptr<HttpResponse>& response)
        : response(response)
        , body(response->body()
        { }
    
        std::shared_ptr<HttpResponse> response;
        std::string_view              body;
    };
    

    现在,在您的代码中,您需要创建其中一个 BodyCustodian 对象,该对象拥有自己的 response(以便正文后面的字符永不过期)和一个 body 这是实际的string_view 你想返回。我们构造其中一个,然后使用别名shared_ptr 构造函数创建指向body 的指针(BodyCustodian 的一个元素,只要保管人还活着就有效),它“拥有”保管人。

    std::shared_ptr<std::string_view>, download_data(){
        std::shared_ptr<BodyCustodian> custodian = std::make_shared<BodyCustodian>(some_http_client->send_request("some_link"));
        return std::shared_ptr<std::string_view>(custodian, &custodian->body);
    }
    

    此共享指针拥有保管人(使响应保持活动状态),因此body 字符串视图仍然有效。

    这种方法确实需要在堆上创建一个小对象(大小约为 6 个指针)。这通常很快,并且不依赖于正文的长度(这是您在复制到 std::string 时担心的问题)。我在这里使用make_shared 来确保我创建了一个~6 指针大小的对象,而不是为BodyCustodian 分配~4 个指针的空间,然后为shared_ptr 控制块分配~2 个指针的空间。 make_shared 很聪明,可以一起做。

    【讨论】:

    • 我不存储响应,它们会被立即处理并丢弃。但是我可以在 std::string 中 std::move() 正文,然后返回吗?避免任何副本?
    • 这样做不会改变任何事情。 std::move 会将其转换为string_view&amp;&amp;,但被选中的string 构造函数仅使用const string_view&amp;,因此它会立即恢复为复制。但是,我们这里所说的是复制 2 个指针而不是复制字符。字符的复制发生在 string 构造函数中,而不是在参数处理中。
    • 如果您只是立即处理和丢弃数据,那么您的用例基本上就是 shared_ptr 的别名构造函数所支持的。充分利用这一点。
    • 我得到错误没有匹配的构造函数,函数的返回值为std::shared_ptr&lt;std::string_view&gt;,响应为std::shared_ptr&lt;HttpResponse&gt;,response->body() 返回std::string_view
    • @MaxPaython string_view a; 是这个函数调用的局部变量,当函数返回时它会被销毁,因此指向它的指针将变得无效(因此你必须在堆上分配它)跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 2013-02-02
    • 2016-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多