Return shared_ptr 内 string_view 的值优化
Return value optimization for string_view inside shared_ptr
难以言表,直接上半伪代码
我有一个下载函数 (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
,return是一个shared_ptr
作为响应,这个响应(我排除了HTTP错误处理的代码,假设它是200。),包含一个response->body()
,这是一个std::string_view
.
此代码工作正常,但是,我想确保下载的数据不会在每次 调用/ returned 时被复制。
我的主要问题:
- 我当前使用的代码是否经过 return 值优化? (有什么需要做的吗?)
- 如果没有,我可以 return
return response->body();
吗? shared_ptr
中的 string_view
在函数 return 之后有效吗?
我在旧版本代码中考虑或使用的东西:
- 返回
std::string
(使用另一个 returned std::string
作为正文的 http 客户端)。
- 返回
std::move
。
- 不用写函数,直接把函数体调用这个函数的地方全部替换掉,直接用
response->body
,避免return(我讨厌)。
正确的做法是什么?
我的工具链:
Ubuntu 20.04 (GLIBC 2.31
), g++ 10.2
, C++20
.
您的代码将使用 RVO。它 returns 是与函数 returns 相同类型的临时变量,它是 cases 其中 RVO 是必需的之一。
当然,它仍然需要一份数据副本,作为接受 string_view
作为参数的 string
构造函数的一部分。
您不能单独传递 string_view
。它只不过是指向其他人数据的一对指针。根据您的代码,这几乎可以肯定是 response
拥有的数据,在您可以使用 returned 的 string_view
之前,这些数据将过期。
你基本上有两个选择。您可以复制数据,也可以保留数据。您当前的代码将它复制一次(感谢 RVO),因此它是我们在这种情况下所能得到的最理想的。但是,还有另一种方法。我们可以 return 一个指向字符串视图的“别名”共享指针。将您的函数 return 设为 std::shared_ptr<std::string_view>
,我们将进行设置以使其正常工作。
shared_ptr<T>
的别名构造函数如下所示:
template <typename Y>
shared_ptr(const shared_ptr<Y>& custodian, T* ward)
它创建了一个 shared_ptr
,当取消引用时,它指向病房。但是,它“拥有”可以是任何其他类型的保管人。在这个共享指针被销毁之前,保管人不会被销毁。
要使用它,我们必须创建一个新的 class 来包装一个 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
(这样正文后面的字符永不过期)和一个 body
这是你想要 return 的实际 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 个指针大小的 space 然后再分配 ~2 个指针大小的 space 用于 shared_ptr
控制块。 make_shared
足够聪明,可以一起做。
难以言表,直接上半伪代码
我有一个下载函数 (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
,return是一个shared_ptr
作为响应,这个响应(我排除了HTTP错误处理的代码,假设它是200。),包含一个response->body()
,这是一个std::string_view
.
此代码工作正常,但是,我想确保下载的数据不会在每次 调用/ returned 时被复制。
我的主要问题:
- 我当前使用的代码是否经过 return 值优化? (有什么需要做的吗?)
- 如果没有,我可以 return
return response->body();
吗?shared_ptr
中的string_view
在函数 return 之后有效吗?
我在旧版本代码中考虑或使用的东西:
- 返回
std::string
(使用另一个 returnedstd::string
作为正文的 http 客户端)。 - 返回
std::move
。 - 不用写函数,直接把函数体调用这个函数的地方全部替换掉,直接用
response->body
,避免return(我讨厌)。
正确的做法是什么?
我的工具链:
Ubuntu 20.04 (GLIBC 2.31
), g++ 10.2
, C++20
.
您的代码将使用 RVO。它 returns 是与函数 returns 相同类型的临时变量,它是 cases 其中 RVO 是必需的之一。
当然,它仍然需要一份数据副本,作为接受 string_view
作为参数的 string
构造函数的一部分。
您不能单独传递 string_view
。它只不过是指向其他人数据的一对指针。根据您的代码,这几乎可以肯定是 response
拥有的数据,在您可以使用 returned 的 string_view
之前,这些数据将过期。
你基本上有两个选择。您可以复制数据,也可以保留数据。您当前的代码将它复制一次(感谢 RVO),因此它是我们在这种情况下所能得到的最理想的。但是,还有另一种方法。我们可以 return 一个指向字符串视图的“别名”共享指针。将您的函数 return 设为 std::shared_ptr<std::string_view>
,我们将进行设置以使其正常工作。
shared_ptr<T>
的别名构造函数如下所示:
template <typename Y>
shared_ptr(const shared_ptr<Y>& custodian, T* ward)
它创建了一个 shared_ptr
,当取消引用时,它指向病房。但是,它“拥有”可以是任何其他类型的保管人。在这个共享指针被销毁之前,保管人不会被销毁。
要使用它,我们必须创建一个新的 class 来包装一个 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
(这样正文后面的字符永不过期)和一个 body
这是你想要 return 的实际 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 个指针大小的 space 然后再分配 ~2 个指针大小的 space 用于 shared_ptr
控制块。 make_shared
足够聪明,可以一起做。