Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/126.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 什么时候更喜欢常量左值引用而不是右值引用模板_C++_C++11_Rvalue Reference_Perfect Forwarding - Fatal编程技术网

C++ 什么时候更喜欢常量左值引用而不是右值引用模板

C++ 什么时候更喜欢常量左值引用而不是右值引用模板,c++,c++11,rvalue-reference,perfect-forwarding,C++,C++11,Rvalue Reference,Perfect Forwarding,当前正在读取cpr请求库的代码库: 注意到这个库的接口经常使用完美转发。只是学习右值参考,所以这对我来说都是比较新的 据我所知,右值引用、模板化和转发的好处在于,被包装的函数调用将通过右值引用而不是通过值获取其参数。这样可以避免不必要的复制。它还可以防止由于引用推断而产生大量重载 然而,根据我的理解,常量左值引用本质上做了同样的事情。它防止了重载的需要,并通过引用传递所有内容。需要注意的是,如果所包装的函数采用非常量引用,它将无法编译 然而,如果调用堆栈中的所有内容都不需要非常量引用,那么为什

当前正在读取cpr请求库的代码库:

注意到这个库的接口经常使用完美转发。只是学习右值参考,所以这对我来说都是比较新的

据我所知,右值引用、模板化和转发的好处在于,被包装的函数调用将通过右值引用而不是通过值获取其参数。这样可以避免不必要的复制。它还可以防止由于引用推断而产生大量重载

然而,根据我的理解,常量左值引用本质上做了同样的事情。它防止了重载的需要,并通过引用传递所有内容。需要注意的是,如果所包装的函数采用非常量引用,它将无法编译

然而,如果调用堆栈中的所有内容都不需要非常量引用,那么为什么不通过常量左值引用传递所有内容呢

我想我这里的主要问题是,什么时候应该使用一个而不是另一个来获得最佳性能?尝试使用以下代码测试此操作。得到了以下相对一致的结果:

编译器:GCC6.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 value签名稍微慢一点,尽管我不知道确切的原因,除非我误解了这一点,并且const l value引用实际上复制了右值

对于左值arg,我们看到计数器,右值转发较慢。为什么会这样?引用演绎不应该总是产生对左值的引用吗?如果是这样的话,就性能而言,它不应该或多或少等同于常量左值引用吗

#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;

}
#包括
#包括
#包括
#包括
std::string func1(常量std::string和arg){
标准:字符串测试(arg);
回归试验;
}
模板
std::string func2(T&&arg){
std::字符串测试(std::forward(arg));
回归试验;
}
void wrap1(常量std::string和arg){
func1(arg);
}
模板
无效wrap2(T&&arg){
func2(std::forward(arg));
}
int main()
{
自动n=100000000;
///传递右值
std::cout首先,结果与您的代码略有不同。如注释中所述,编译器及其设置非常重要。特别是,您可能会注意到,所有情况下都有类似的运行时,但第一种情况除外,它的运行速度大约是您的两倍

Passing rvalue!
const l value: 1357465
rvalue forwarding: 669589
Passing lvalue!
const l value: 744105
rvalue forwarding: 713189
让我们来看看每种情况下到底发生了什么

1) 调用
wrap1(“test”)
时,由于该函数的签名需要
const std::string&
,因此每次调用(即
n次)时,您传递的字符数组都将隐式转换为临时
std::string
对象,其中包含一个值的副本*。然后,对该临时变量的常量引用将被传递到
func1
,在此基础上构造另一个
std::string
,该字符串再次包含一个副本(因为它是常量引用,所以不能从中移动,尽管它实际上是临时变量)。即使函数按值返回,但由于RVO,如果使用返回值,该副本将保证被省略。在这种情况下,不使用返回值,并且我不完全确定该标准是否允许编译器优化
temp
的构造。我怀疑不会,因为通常这种构造可以d有明显的副作用(你的结果表明它不会被优化掉)。总之,在这种情况下,对
std::string
进行两次完整的构造和销毁

2) 调用
wrap2(“test”)
时,参数类型为
const char[5]
,它作为一个右值引用一直转发到
func2
,其中
std::string
构造函数来自
const char[]
被调用以复制值。模板参数
T
的推导类型是
const char[5]&&
,很明显,尽管它是一个右值引用,但无法将其移动(因为它既是
const
又不是
std::string
)。与前一种情况相比,每次调用只会构造/销毁一次字符串(const char[5]
literal始终在内存中,不会产生开销)

3) 调用
wrap1(arg)
时,通过链传递一个左值作为
const字符串&
,并在
func1
中调用一个副本构造函数

4) 调用
wrap2(arg)
时,这与前面的情况类似,因为
T
的推断类型是
const std::string&

5) 我假设您的测试旨在演示在调用链的底部创建参数副本(因此创建了
temp
)时完美转发的优势。在这种情况下,您需要将前两种情况下的
“test”
参数替换为
std::string(“test”)
以便真正拥有
std::string&
参数,并将您的完美转发修改为
std::forward(arg)
,如注释中所述。在这种情况下,如下所示:

这与我们之前的类似,但现在实际上调用了一个移动构造函数

我希望这有助于解释结果。可能还有一些其他问题与函数调用的内联和其他编译器优化有关,这将有助于解释案例2-4之间较小的差异

至于你的问题是使用哪种方法,我建议阅读Scott Meyer的“有效的现代C++”第23-30项。抱歉,没有直接的答案,而是参考书,但没有银弹,最佳选择总是视情况而定,因此最好只了解每个设计决策的权衡


*复制构造函数可以
Passing rvalue!
const l value: 1314630
rvalue forwarding: 595084
Passing lvalue!
const l value: 712461
rvalue forwarding: 720338