哪个更好:返回元组或传递参数作为引用?

时间:2017-12-13 10:35:54

标签: c++ function

我创建了代码,其中有两个函数returnValuesreturnValuesVoid。一个返回2个值的元组和其他接受参数对函数的引用。

#include <iostream>
#include <tuple>

std::tuple<int, int> returnValues(const int a, const int b) {
    return std::tuple(a,b);
}

void returnValuesVoid(int &a,int &b) {
    a += 100;
    b += 100;
}

int main() {
    auto [x,y] = returnValues(10,20);

    std::cout << x ;
    std::cout << y ;

    int a = 10, b = 20;
    returnValuesVoid(a, b);

    std::cout << a ;
    std::cout << b ;
}

我读到http://en.cppreference.com/w/cpp/language/structured_binding 它可以破坏元组到auto [x,y]个变量。

auto [x,y] = returnValues(10,20);比引用更好吗?我知道它的速度较慢,因为它必须返回元组对象引用才能处理传递给函数的原始变量,因此除了更清晰的代码之外没有理由使用它。

由于auto [x,y] ,因为C ++ 17 ,人们会在制作中使用它吗?我看到它看起来比returnValuesVoid看起来更干净,它是无效的类型,但它是否比通过引用传递有其他优势?

4 个答案:

答案 0 :(得分:3)

专注于更具可读性和哪种方法为读者提供更好的直觉,请保留您可能认为在后台出现的性能问题。

返回一个元组(或一对,一个结构等)的函数正在向作者大喊该函数返回一些东西,几乎总是有一些用户可以考虑的含义。

一个函数可以通过引用传递给变量的结果,可能会让疲惫不堪的读者注意到它。

因此,通常,更喜欢通过元组返回结果。

Mike van Dyke指出了link

  

F.21:要返回多个“out”值,更喜欢返回元组或结构

     

原因

     

返回值是自我记录为“仅输出”   值。请注意,按照惯例,C ++确实有多个返回值   使用元组(包括对),可能有额外的便利   在呼叫站点打领带。

     

[...]

     

异常

     

有时,我们需要将一个对象传递给一个函数来操纵它的状态。在这种情况下,通过引用T&传递对象通常是正确的技术。

答案 1 :(得分:2)

看看反汇编(用GCC -O3编译):

实现元组调用需要更多指令。

0000000000000000 <returnValues(int, int)>:
   0:   83 c2 64                add    $0x64,%edx
   3:   83 c6 64                add    $0x64,%esi
   6:   48 89 f8                mov    %rdi,%rax
   9:   89 17                   mov    %edx,(%rdi)
   b:   89 77 04                mov    %esi,0x4(%rdi)
   e:   c3                      retq   
   f:   90                      nop

0000000000000010 <returnValuesVoid(int&, int&)>:
  10:   83 07 64                addl   $0x64,(%rdi)
  13:   83 06 64                addl   $0x64,(%rsi)
  16:   c3                      retq   

但是对于元组调用者的指示较少:

0000000000000000 <callTuple()>:
   0:   48 83 ec 18             sub    $0x18,%rsp
   4:   ba 14 00 00 00          mov    $0x14,%edx
   9:   be 0a 00 00 00          mov    $0xa,%esi
   e:   48 8d 7c 24 08          lea    0x8(%rsp),%rdi
  13:   e8 00 00 00 00          callq  18 <callTuple()+0x18> // call returnValues
  18:   8b 74 24 0c             mov    0xc(%rsp),%esi
  1c:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  23:   e8 00 00 00 00          callq  28 <callTuple()+0x28> // std::cout::operator<<
  28:   8b 74 24 08             mov    0x8(%rsp),%esi
  2c:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  33:   e8 00 00 00 00          callq  38 <callTuple()+0x38> // std::cout::operator<<
  38:   48 83 c4 18             add    $0x18,%rsp
  3c:   c3                      retq   
  3d:   0f 1f 00                nopl   (%rax)

0000000000000040 <callRef()>:
  40:   48 83 ec 18             sub    $0x18,%rsp
  44:   48 8d 74 24 0c          lea    0xc(%rsp),%rsi
  49:   48 8d 7c 24 08          lea    0x8(%rsp),%rdi
  4e:   c7 44 24 08 0a 00 00    movl   $0xa,0x8(%rsp)
  55:   00 
  56:   c7 44 24 0c 14 00 00    movl   $0x14,0xc(%rsp)
  5d:   00 
  5e:   e8 00 00 00 00          callq  63 <callRef()+0x23> // call returnValuesVoid
  63:   8b 74 24 08             mov    0x8(%rsp),%esi
  67:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  6e:   e8 00 00 00 00          callq  73 <callRef()+0x33> // std::cout::operator<<
  73:   8b 74 24 0c             mov    0xc(%rsp),%esi
  77:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  7e:   e8 00 00 00 00          callq  83 <callRef()+0x43> // std::cout::operator<<
  83:   48 83 c4 18             add    $0x18,%rsp
  87:   c3                      retq   

我认为没有任何相当大的性能,但元组更清晰,更易读。

也试过内联电话,绝对没有什么不同。它们都生成完全相同的汇编代码。

0000000000000000 <callTuple()>:
   0:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
   7:   48 83 ec 08             sub    $0x8,%rsp
   b:   be 6e 00 00 00          mov    $0x6e,%esi
  10:   e8 00 00 00 00          callq  15 <callTuple()+0x15>
  15:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  1c:   be 78 00 00 00          mov    $0x78,%esi
  21:   48 83 c4 08             add    $0x8,%rsp
  25:   e9 00 00 00 00          jmpq   2a <callTuple()+0x2a> // TCO, optimized way to call a function and also return
  2a:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

0000000000000030 <callRef()>:
  30:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  37:   48 83 ec 08             sub    $0x8,%rsp
  3b:   be 6e 00 00 00          mov    $0x6e,%esi
  40:   e8 00 00 00 00          callq  45 <callRef()+0x15>
  45:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi
  4c:   be 78 00 00 00          mov    $0x78,%esi
  51:   48 83 c4 08             add    $0x8,%rsp
  55:   e9 00 00 00 00          jmpq   5a <callRef()+0x2a> // TCO, optimized way to call a function and also return

答案 2 :(得分:1)

使用另一个编译器(VS 2017),生成的代码没有任何区别,因为函数调用只是优化了。

int main() {
00007FF6A9C51E50  sub         rsp,28h  
    auto [x,y] = returnValues(10,20);

    std::cout << x ;
00007FF6A9C51E54  mov         edx,0Ah  
00007FF6A9C51E59  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A9C51F60h)  
    std::cout << y ;
00007FF6A9C51E5E  mov         edx,14h  
00007FF6A9C51E63  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A9C51F60h)  

    int a = 10, b = 20;
    returnValuesVoid(a, b);

    std::cout << a ;
00007FF6A9C51E68  mov         edx,6Eh  
00007FF6A9C51E6D  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A9C51F60h)  
    std::cout << b ;
00007FF6A9C51E72  mov         edx,78h  
00007FF6A9C51E77  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A9C51F60h)  
}
00007FF6A9C51E7C  xor         eax,eax  
00007FF6A9C51E7E  add         rsp,28h  
00007FF6A9C51E82  ret  

因此,使用更清晰的代码似乎是显而易见的选择。

答案 3 :(得分:0)

Zang所说的是真的,但并非直截了当。我运行有问题的chrono提供的代码来测量时间。我认为答案需要在观察发生了什么之后进行编辑。

对于1M迭代,函数通过引用调用所花费的时间为3毫秒,而通过std::tiestd::tuple组合调用所花费的时间约为94ms。

尽管在实践中差异似乎很小,但是元组1的执行速度会稍慢。因此,对于性能密集型系统,我建议使用按引用调用。

我的代码:

#include <iostream>
#include <tuple>
#include <chrono>

std::tuple<int, int> returnValues(const int a, const int b)
{
    return std::tuple<int, int>(a, b);
}

void returnValuesVoid(int &a, int &b)
{
    a += 100;
    b += 100;
}

int main()
{
    int a = 10, b = 20;
    auto begin = std::chrono::high_resolution_clock::now();
    int x, y;
    for (int i = 0; i < 1000000; i++)
    {
        std::tie(x, y) = returnValues(a, b);
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << double(std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count()) << '\n';

    a = 10;
    b = 20;
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000000; i++)
    {
        returnValuesVoid(a, b);
    }
    auto stop = std::chrono::high_resolution_clock::now();
    std::cout << double(std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count()) << '\n';
}