shared_ptr 中 string_view 的返回值优化

问题描述

很难用语言表达所以我直接跳到半伪代码中。

我有一个下载函数 (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->body() ,这是一个 std::string_view

代码工作正常,但是,我想确保每次调用函数时都不会复制下载的数据/返回

我的主要问题:

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

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

  • 返回 std::string(使用另一个返回 std::string 作为正文的 http 客户端)。
  • std::move一起返回。
  • 与其写一个函数,不如把函数调用这个函数的地方全部替换掉​​,直接使用response->body,避免返回(我讨厌它)。

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

我的工具链:

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

解决方法

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

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

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

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

shared_ptr<T> 的别名构造函数如下所示:

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

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

要使用它,我们必须创建一个新类,它包含一个 shared_ptr<HttpResponse> 和一个引用响应中数据的主体 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(以便正文后面的字符永不过期)和一个 {{1 }} 这是您要返回的实际 string_view。我们构造其中之一,然后使用别名 body 构造函数创建一个指向 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); } 字符串视图仍然有效。

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...