从C++函数返回多个值

366

从C++函数中返回多个值时是否有一种首选的方法?例如,想象一个将两个整数相除并返回商和余数的函数。我经常见到的一种方式是使用引用参数:

void divide(int dividend, int divisor, int& quotient, int& remainder);

一种变体是返回一个值,并通过引用参数传递另一个值:

int divide(int dividend, int divisor, int& remainder);

另一种方法是声明一个结构体来包含所有的结果,然后返回它:


struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

这些方式中是否有一种通常更受青睐,或者还有其他建议?

编辑:在实际的代码中,可能会有超过两个结果。它们也可能是不同的类型。

23个回答

5

我正在编写一个返回多个值(超过两个)的C++程序。该程序可在C++14(G++4.9.2)中执行,类似于计算器。

#  include <tuple>
# include <iostream>

using namespace std; 

tuple < int,int,int,int,int >   cal(int n1, int n2)
{
    return  make_tuple(n1/n2,n1%n2,n1+n2,n1-n2,n1*n2);
}

int main()
{
    int qut,rer,add,sub,mul,a,b;
    cin>>a>>b;
    tie(qut,rer,add,sub,mul)=cal(a,b);
    cout << "quotient= "<<qut<<endl;
    cout << "remainder= "<<rer<<endl;
    cout << "addition= "<<add<<endl;
    cout << "subtraction= "<<sub<<endl;
    cout << "multiplication= "<<mul<<endl;
    return 0;
}

因此,您可以清楚地理解,通过这种方式,您可以从函数中返回多个值。使用std::pair只能返回2个值,而std::tuple可以返回两个以上的值。


4
使用C++14,你也可以在cal的返回类型上使用auto,使其更加简洁。(我的看法) - sfjac

5
请使用结构体或类作为返回值。现在使用std::pair可能有效,但是:
  1. 如果您稍后决定要返回更多信息,则它不够灵活。
  2. 从头文件中的函数声明中并不能很清楚地知道返回什么以及以何种顺序返回。
使用具有自我描述成员变量名称的结构体作为返回值,对于任何使用您的函数的人来说,这可能会少出现一些错误。假设我是您的同事,作为潜在的函数用户,我能够在2秒内立即理解您的divide_result结构体。使用输出参数或神秘的二元组和三元组将需要更长的时间来阅读,并且可能被使用不正确。而且即使使用了几次该函数后,我也很可能仍然记不住参数的正确顺序。

4
如果你的函数通过引用返回一个值,编译器就不能在调用其他函数时将其存储在寄存器中,因为理论上第一个函数可以将传递给它的变量的地址保存在全局可访问的变量中,而任何后续调用的函数都可能更改它,因此编译器必须(1)在调用其他函数之前将值从寄存器保存回内存,并且(2)在任何这样的调用之后再次从内存中重新读取它。
如果你通过引用返回值,你的程序优化将受到影响。

4

以下是这个话题上的“核心指南”(由Bjarne Stroustrup和Herb Sutter编写)链接。

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-out-multi

部分引用:

F.21:为了返回多个“输出”值,建议返回一个结构体或元组

原因:返回值是自说明的“仅输出”值。请注意,C++确实具有多个返回值,通过使用元组的约定(包括pair),可能在调用点处使用tie或结构化绑定(C++17)的额外便利性。在返回值有语义的情况下,最好使用具名结构体。否则,在通用代码中,无名元组很有用。


希望这个答案能够快速得到认可并成为最佳答案。 - Markus Dutschke

3

我倾向于在这样的函数中使用out-vals,因为我坚持函数返回成功/错误代码的范例,而且我喜欢保持事物的一致性。


2
你为什么坚持使用具有多个返回值的函数?通过面向对象编程,你可以使用一个提供单个返回值的常规函数的类,并且可以使用任意数量的附加“返回值”,如下所示。优点是调用者可以选择查看额外的数据成员,但不需要这样做。这是复杂的数据库或网络调用的首选方法,在这些情况下可能需要大量的附加返回信息以防发生错误。
回答你最初的问题,这个例子有一个方法返回商,这是大多数调用者可能需要的,此外,在方法调用之后,你可以将余数作为数据成员获取。
class div{
   public:
      int remainder;

      int quotient(int dividend, int divisor){
         remainder = ...;
         return ...;
      }
};

1
我认为有些情况下这是低效的。例如,您有一个单独的for循环生成多个返回值。如果将这些值拆分为单独的函数,则需要为每个值运行一次循环。 - jiggunjer
1
@jiggunjer 你可以运行循环一次,并将多个返回值存储在单独的类数据成员中。这突显了面向对象编程概念的灵活性。 - Roland

2

与其返回多个值,不如只返回其中一个,并在所需的函数中引用其他值,例如:

int divide(int a,int b,int quo,int &rem)

我在问题本身中没有提到吗?另外,请查看我的答案中的反对意见。 - Fred Larson

2

可选的替代方案包括数组、生成器控制反转,但这里都不适用。

有些人(例如历史上的 Win32 中的 Microsoft)倾向于使用引用参数是为了简单,因为它清楚地说明了谁分配了内存,以及它在堆栈中的情况,减少了结构体的数量,并允许一个单独的返回值表示成功。

“纯粹”的程序员更喜欢使用结构体,假设它就是函数的返回值(正如在这里的情况一样),而不是函数偶然会修改的东西。如果你有一个更复杂的过程,或者涉及状态的问题,你可能会使用引用(假设你有不使用类的理由)。


2
我会说没有首选方法,一切取决于您要如何处理响应。如果结果将在进一步处理中一起使用,则结构是有意义的,否则我倾向于将它们作为单独的引用传递,除非该函数将用于复合语句:
x = divide(x,y,z)+ divide(a,b,c);
通常,我选择在参数列表中通过引用传递 'out structures' ,而不是返回一个新结构的传递副本开销(但这是微不足道的)。
void divide(int dividend, int divisor, Answer &ans)
输出参数是否令人困惑? 以引用方式发送的参数表明值将发生更改(与const引用相对)。 合理的命名也可以消除混淆。

1
我认为这有点令人困惑。调用代码的人看到“divide(a,b,c);”,并没有表明c是一个输出值,除非他们查找函数签名。但这是对非const引用参数的一般担忧,而不是特定于这个问题。 - Steve Jessop

2
Boost元组将成为我从函数返回多个值的通用系统的首选选择。
可能的例子:
include "boost/tuple/tuple.hpp"

tuple <int,int> divide( int dividend,int divisor ) 

{
  return make_tuple(dividend / divisor,dividend % divisor )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接