【发布时间】:2018-02-20 10:40:17
【问题描述】:
目前正在阅读 cpr requests 库的代码库: https://github.com/whoshuu/cpr/blob/master/include/cpr/api.h
注意到这个库的接口经常使用完美转发。只是学习右值引用,所以这对我来说都是相对较新的。
据我了解,右值引用、模板化和转发的好处在于,被包裹的函数调用将通过右值引用而不是值来获取其参数。这避免了不必要的复制。它还可以防止由于引用扣除而不得不生成一堆重载。
但是,据我了解, const 左值引用本质上是做同样的事情。它可以防止重载并通过引用传递所有内容。需要注意的是,如果被包装的函数采用非常量引用,它将无法编译。
但是,如果调用堆栈中的所有内容都不需要非常量引用,那么为什么不直接通过 const 左值引用传递所有内容呢?
我想我的主要问题是,什么时候应该使用其中一个来获得最佳性能?尝试使用以下代码对此进行测试。得到了以下比较一致的结果:
编译器:gcc 6.3 操作系统:Debian GNU/Linux 9
<<<<
Passing rvalue!
const l value: 2912214
rvalue forwarding: 2082953
Passing lvalue!
const l value: 1219173
rvalue forwarding: 1585913
>>>>
这些结果在运行之间保持相当一致。似乎对于右值 arg,const l 值签名稍微慢一些,尽管我不完全确定为什么,除非我误解了这一点并且 const 左值引用实际上复制了右值。
对于左值 arg,我们看到计数器,右值转发更慢。为什么会这样?引用推导不应该总是产生对左值的引用吗?如果是这样的话,它在性能方面不应该或多或少等同于 const 左值引用吗?
#include <iostream>
#include <string>
#include <utility>
#include <time.h>
std::string func1(const std::string& arg) {
std::string test(arg);
return test;
}
template <typename T>
std::string func2(T&& arg) {
std::string test(std::forward<T>(arg));
return test;
}
void wrap1(const std::string& arg) {
func1(arg);
}
template <typename T>
void wrap2(T&& arg) {
func2(std::forward<T>(arg));
}
int main()
{
auto n = 100000000;
/// Passing rvalue
std::cout << "Passing rvalue!" << std::endl;
// Test const l value
auto t = clock();
for (int i = 0; i < n; ++i)
wrap1("test");
std::cout << "const l value: " << clock() - t << std::endl;
// Test rvalue forwarding
t = clock();
for (int i = 0; i < n; ++i)
wrap2("test");
std::cout << "rvalue forwarding: " << clock() - t << std::endl;
std::cout << "Passing lvalue!" << std::endl;
/// Passing lvalue
std::string arg = "test";
// Test const l value
t = clock();
for (int i = 0; i < n; ++i)
wrap1(arg);
std::cout << "const l value: " << clock() - t << std::endl;
// Test rvalue forwarding
t = clock();
for (int i = 0; i < n; ++i)
wrap2(arg);
std::cout << "rvalue forwarding: " << clock() - t << std::endl;
}
【问题讨论】:
-
为了清楚起见,转发引用(
T&&- 作为演绎上下文中的模板类型参数(又名函数参数)与 r 值引用不同(如 @987654325 @)。前者将绑定到任何东西,后者仅绑定到临时对象。 -
哦,当您发布基准测试结果时,请同时添加编译器,它的版本和编译标志。
-
std::forward<T&>(arg)应该是std::forward<T>(arg)。很抱歉向 cmets 发送垃圾邮件。 -
重要的区别是你假设的函数调用参数是否会被函数体以外的任何东西再次查看。如果没有,当需要私有副本时,转发允许您将值移动到函数内的任何潜在消费者,而您建议的替代方案总是会产生“完整”副本。如果函数不消耗任何东西,那么就没有区别。
标签: c++ c++11 rvalue-reference perfect-forwarding