从C ++函数返回多个值


242

有没有从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);

是通常首选这些方法之一,还是有其他建议?

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

Answers:


216

为了返回两个值,我使用了std::pair(通常是typedef'd)。您应该查看boost::tuple(在C ++ 11及更高版本中,有std::tuple)两个以上的返回结果。

随着C ++ 17中结构化绑定的引入,返回std::tuple可能应该成为公认的标准。


12
为元组+1。请记住,以结构返回的大型对象与通过引用传递的大型对象的性能差异。
Marcin

12
如果要使用元组,为什么不也将它们用于配对。为什么有特殊情况?
Ferruccio

4
弗雷德,是的boost :: tuple可以做到这一点:)
Johannes Schaub-litb

46
在C ++ 11中,您可以使用std::tuple
Ferruccio

14
如果要从一个函数接受多个值,一种简便的方法是使用std::tie stackoverflow.com/a/2573822/502144
fdermishin 2014年

175

在C ++ 11中,您可以:

#include <tuple>

std::tuple<int, int> divide(int dividend, int divisor) {
    return  std::make_tuple(dividend / divisor, dividend % divisor);
}

#include <iostream>

int main() {
    using namespace std;

    int quotient, remainder;

    tie(quotient, remainder) = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}

在C ++ 17中:

#include <tuple>

std::tuple<int, int> divide(int dividend, int divisor) {
    return  {dividend / divisor, dividend % divisor};
}

#include <iostream>

int main() {
    using namespace std;

    auto [quotient, remainder] = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}

或使用结构:

auto divide(int dividend, int divisor) {
    struct result {int quotient; int remainder;};
    return result {dividend / divisor, dividend % divisor};
}

#include <iostream>

int main() {
    using namespace std;

    auto result = divide(14, 3);

    cout << result.quotient << ',' << result.remainder << endl;

    // or

    auto [quotient, remainder] = divide(14, 3);

    cout << quotient << ',' << remainder << endl;
}

4
我对函数返回元组有一个担忧。假设上面的函数原型位于标头中,那么在不了解函数定义的情况下,如何知道第一和第二个返回值是什么意思呢?余数或余数。
Uchia Itachi

7
@UchiaItachi同样关注函数参数,可以给它们命名,但是语言甚至不强制执行,并且在读取时,参数名称在调用站点中没有值。同样,在一次返回中,您只有一个类型,但是拥有名称也可能有用,使用元组时,问题只会增加一倍,因此,imo,该语言缺少以多种方式进行自我记录的语言,不仅如此。
pepper_chico

1
如果我想明确指定divide()的返回类型,最后一个示例将如何?然后我应该在其他地方定义结果,还是可以在返回类型说明中正确地定义它?
斯拉瓦

1
@Slava你不能在函数签名定义类型的权利,你就必须声明的类型外,并用它作为返回类型,喜欢它的正常进行(只要将struct线功能体外和替换auto与函数返回result
pepper_chico

3
@pepper_chico如果要将函数定义divide放入单独的cpp文件中怎么办?我得到了错误error: use of ‘auto divide(int, int)’ before deduction of ‘auto’。我该如何解决?
Adriaan

123

就我个人而言,出于多种原因,我通常不喜欢返回参数:

  • 调用中哪些参数是ins和哪些outs并不总是很明显
  • 通常,您必须创建一个局部变量来捕获结果,而返回值可以内联使用(这可能是一个好主意,但可能不是一个好主意,但至少可以选择)
  • 对我来说,对一个功能使用“进门”和“出门”似乎更干净-所有输入都在这里,所有输出都在这里
  • 我想使论点列表尽可能短

我也对结对/元组技术有所保留。通常,返回值通常没有自然顺序。代码的阅读者如何知道result.first是商还是余数?实现者可以更改顺序,这将破坏现有代码。如果值的类型相同,这将特别隐蔽,这样就不会产生编译器错误或警告。实际上,这些参数也适用于返回参数。

这是另一个代码示例,这个例子不那么简单:

pair<double,double> calculateResultingVelocity(double windSpeed, double windAzimuth,
                                               double planeAirspeed, double planeCourse);

pair<double,double> result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.first << endl;
cout << result.second << endl;

这会打印地面速度和航向,还是地面速度和航向?这不是很明显。

比较一下:

struct Velocity {
    double speed;
    double azimuth;
};
Velocity calculateResultingVelocity(double windSpeed, double windAzimuth,
                                    double planeAirspeed, double planeCourse);

Velocity result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.speed << endl;
cout << result.azimuth << endl;

我认为这更清楚。

因此,我认为通常我的首选是结构技术。在某些情况下,对/元组的想法可能是一个很好的解决方案。我想尽可能避免返回参数。


1
宣布struct喜欢的建议Velocity是一个不错的建议。但是,一个问题是它污染名称空间。我猜想在C ++ 11中,struct可以具有长类型名称,并且可以使用auto result = calculateResultingVelocity(...)
Hugues

5
+1。函数应返回一个 “事物”,而不是按某种顺序排列的“事物元组”。
DevSolar 2013年

1
由于此答案中所述的原因,我更喜欢结构而不是std :: pairs / std :: tuples。但是我也不喜欢名称空间“污染”。对我来说,理想的解决方案是返回类似的匿名结构struct { int a, b; } my_func();。可以这样使用:auto result = my_func();。但是C ++不允许这样做:“新类型不能在返回类型中定义”。所以我必须创建类似struct my_func_result_t...的结构
anton_rh 16-4-21

2
@anton_rh:C ++ 14允许使用返回本地类型auto,因此auto result = my_func();很容易获得。
ildjarn

4
大约15年前,当我们发现boost时,由于它非常方便,所以我们经常使用tuple。随着时间的流逝,我们遇到了可读性方面的缺点,特别是对于具有相同类型的元组(例如,tuple <double,double>;哪个是哪个)。因此,最近我们更习惯于引入一个小的POD结构,其中成员变量的名称至少表明某种意义。
gast128

24
std::pair<int, int> divide(int dividend, int divisor)
{
   // :
   return std::make_pair(quotient, remainder);
}

std::pair<int, int> answer = divide(5,2);
 // answer.first == quotient
 // answer.second == remainder

std :: pair本质上是您的struct解决方案,但是已经为您定义了,并准备适应任何两种数据类型。


3
这将适用于我的简单示例。但是,通常,可能会返回两个以上的值。
Fred Larson

5
也不能自我记录。您还记得DIV的其余x86寄存器吗?
标记

1
@Mark-我同意位置解决方案的维护性较差。您可能会遇到“置换和阻碍”问题。
Fred Larson

16

它完全取决于实际功能和多个值的含义及其大小:

  • 如果它们与您的分数示例中的内容相关,那么我将使用结构或类实例。
  • 如果它们不是真正相关,并且不能分组为一个类/结构,那么您应该将方法重构为两个。
  • 根据您要返回的值在内存中的大小,您可能希望返回一个指向类实例或结构的指针,或使用引用参数。

1
我喜欢您的回答,您的最后一个项目符号使我想起了我刚刚读到的内容,根据情况的不同,按值传递的速度越来越快,这使情况变得更加复杂... cpp-next.com/archive/2009/08/want-speed-pass值
贤者

12

OO解决方案是创建一个比率类。它将不需要任何额外的代码(将节省一些代码),会变得更加清晰/清晰,并且会为您提供一些额外的重构,从而使您也可以清除此类之外的代码。

实际上,我认为有人建议返回一个结构,该结构足够接近,但隐藏了一个意图,即它需要是一个经过深思熟虑的类,具有构造函数和一些方法,实际上是您最初提到的“方法”(作为返回对)应该很可能是此类的成员,并返回其自身的实例。

我知道您的示例只是一个“示例”,但事实是,除非您的函数做的比任何函数都要做的要多,否则如果您希望它返回多个值,则几乎肯定会丢失一个对象。

不要害怕创建这些小类来完成一些小工作-这就是OO的魔力-您最终会分解它,直到每种方法都非常小而简单,每个类又小又易于理解。

另一件事本来可以指示出错误的迹象:在OO中,您基本上没有数据-OO并不是要传递数据,类需要在内部管理和操纵自己的数据,任何传递的数据(包括访问器)表示您可能需要重新考虑一些事情。


10

在C(因此是C ++)标准中div,使用ldiv(或在lldiv)中返回函数<stdlib.h>(在C99中是)是有先例的<cstdlib>

“返回值和返回参数的混合”通常最不干净。

在C语言中,让函数返回状态并通过返回参数返回数据是明智的;在C ++中,您显然可以使用异常来代替故障信息,这显然不太明智。

如果返回值多于两个,则类似结构的机制可能是最好的。


10

使用C ++ 17,您还可以返回一个或多个不可移动/不可复制的值(在某些情况下)。返回不可移动类型的可能性是通过新的保证的返回值优化实现的,它与聚合很好地结合 在一起,也可以称为模板构造函数

template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};

// guide:
template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;

auto f(){ return many{string(),5.7, unmovable()}; }; 

int main(){
   // in place construct x,y,z with a string, 5.7 and unmovable.
   auto [x,y,z] = f();
}

这样做的好处是保证不会引起任何复制或移动。您也可以使示例manystruct可变。更多细节:

返回C ++ 17可变参数模板'构造推论指南'的可变参数聚合(struct)和语法


6

有很多方法可以返回多个参数。我会很高兴的。

使用参考参数:

void foo( int& result, int& other_result );

使用指针参数:

void foo( int* result, int* other_result );

这样做的好处是您必须&在呼叫站点进行操作,可能会提醒人们这是一个超出参数的范围。

编写模板并使用它:

template<class T>
struct out {
  std::function<void(T)> target;
  out(T* t):target([t](T&& in){ if (t) *t = std::move(in); }) {}
  out(std::optional<T>* t):target([t](T&& in){ if (t) t->emplace(std::move(in)); }) {}
  out(std::aligned_storage_t<sizeof(T), alignof(T)>* t):
    target([t](T&& in){ ::new( (void*)t ) T(std::move(in)); } ) {}
  template<class...Args> // TODO: SFINAE enable_if test
  void emplace(Args&&...args) {
    target( T(std::forward<Args>(args)...) );
  }
  template<class X> // TODO: SFINAE enable_if test
  void operator=(X&&x){ emplace(std::forward<X>(x)); }
  template<class...Args> // TODO: SFINAE enable_if test
  void operator()(Args...&&args){ emplace(std::forward<Args>(args)...); }
};

然后我们可以做:

void foo( out<int> result, out<int> other_result )

一切都很好。 foo不再能够读取任何作为奖励传递的值。

可以使用其他方法定义可以放置数据的位置out。例如,将东西放置在某个地方的回调。

我们可以返回一个结构:

struct foo_r { int result; int other_result; };
foo_r foo();

whick在每个版本的C ++和 这还允许:

auto&&[result, other_result]=foo();

零成本。由于保证了精确性,甚至甚至无法移动参数。

我们可以返回一个std::tuple

std::tuple<int, int> foo();

缺点是没有命名参数。这允许

auto&&[result, other_result]=foo();

也一样 先于 我们可以改为:

int result, other_result;
std::tie(result, other_result) = foo();

这有点尴尬。但是,保证省略在这里不起作用。

进入陌生人的领域(这是在out<>!之后),我们可以使用延续传递样式:

void foo( std::function<void(int result, int other_result)> );

现在,呼叫者可以:

foo( [&](int result, int other_result) {
  /* code */
} );

这种样式的好处是您可以返回任意数量的值(具有统一类型),而无需管理内存:

void get_all_values( std::function<void(int)> value )

value当您使用时,回调可能被调用500次get_all_values( [&](int value){} )

出于纯粹的疯狂,您甚至可以在延续上使用延续。

void foo( std::function<void(int, std::function<void(int)>)> result );

其用法如下:

foo( [&](int result, auto&& other){ other([&](int other){
  /* code */
}) });

这将允许result和之间的多对一关系other

再次使用uniforn值,我们可以这样做:

void foo( std::function< void(span<int>) > results )

在这里,我们调用带有结果范围的回调。我们甚至可以重复执行此操作。

使用此功能,您可以具有有效地传递兆字节数据而无需在堆栈外进行任何分配的功能。

void foo( std::function< void(span<int>) > results ) {
  int local_buffer[1024];
  std::size_t used = 0;
  auto send_data=[&]{
    if (!used) return;
    results({ local_buffer, used });
    used = 0;
  };
  auto add_datum=[&](int x){
    local_buffer[used] = x;
    ++used;
    if (used == 1024) send_data();
  };
  auto add_data=[&](gsl::span<int const> xs) {
    for (auto x:xs) add_datum(x);
  };
  for (int i = 0; i < 7+(1<<20); ++i) {
    add_datum(i);
  }
  send_data(); // any leftover
}

现在,std::function这有点繁重,因为我们将在零开销的无分配环境中进行此操作。所以我们想要一个function_view永远不会分配的。

另一个解决方案是:

std::function<void(std::function<void(int result, int other_result)>)> foo(int input);

在这里,不采用回调并调用它,foo而是返回采用回调的函数。

foo(7)([&](int结果,int other_result){/ *代码* /}); 这通过使用单独的括号将输出参数与输入参数分开。

variant协程,您可以foo生成返回类型(或仅返回类型)变体的生成器。语法尚未确定,因此我不举一些例子。

在信号和插槽的世界中,一个公开一组信号的函数:

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();

允许您创建一个foo异步工作的对象,并在完成后广播结果。

沿着这条线,我们有多种流水线技术,其中函数不执行任何操作,而是以某种方式安排数据的连接,并且操作是相对独立的。

foo( int_source )( int_dest1, int_dest2 );

那么此代码将不执行任何操作,直到int_source提供整数为止。当它出现时,int_dest1int_dest2开始接收结果。


该答案包含比其他答案更多的信息!特别是有关auto&&[result, other_result]=foo();用于返回元组和结构的for函数的信息。谢谢!
jjmontes

我很欣赏这个详尽的答案,尤其是因为我仍然坚持使用C ++ 11,因此无法使用其他人提出的一些更现代的解决方案。
GuyGizmo

5

使用结构或类作为返回值。std::pair现在可能可以使用,但是

  1. 如果您稍后决定要返回更多信息,这是不灵活的;
  2. 从标头中的函数声明中还不清楚,要返回的内容和顺序是什么。

对于使用您的函数的人来说,返回具有自记录成员变量名称的结构可能不太容易出错。戴上我的同事帽子一会儿,您的divide_result结构对于我(您可能是该功能的潜在用户)而言很容易在2秒钟后立即理解。混淆输出参数或神秘的对和元组会花费更多的时间来阅读,并且可能会被错误地使用。而且即使在使用函数几次之后,我很可能仍然不记得参数的正确顺序。


4

如果您的函数通过引用返回值,则编译器在调用其他函数时无法将其存储在寄存器中,因为从理论上讲,第一个函数可以将传递给它的变量的地址保存在全局可访问的变量中,并且任何次安全调用的函数都可以更改它,以便编译器将(1)在调用其他函数之前将寄存器中的值保存回内存,以及(2)在进行任何此类调用后再次需要从内存中重新读取它时。

如果您通过引用返回,则程序的优化将受到影响


4

在这里,我正在编写一个程序,该程序在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中,您还可以使用autoreturn type cal使其更整洁。(IMO)。
sfjac 2015年

3

我倾向于在这样的函数中使用超出值,因为我坚持返回成功/错误代码的函数范例,并且我希望保持一致。


2

备选方案包括数组,生成器控制反转,但此处不适合使用。

某些工具(例如,历史悠久的Win32中的Microsoft)为了简化起见倾向于使用参考参数,因为很明显,谁来分配以及它在堆栈上的外观如何,减少了结构的扩散,并为成功设置了单独的返回值。

“纯”程序员更喜欢该结构,假设它函数值(如此处的情况),而不是函数偶然碰到的东西。如果您有一个更复杂的过程,或者带有状态的东西,那么您可能会使用引用(假设您有理由不使用类)。


2

我想说没有首选的方法,这完全取决于您将如何处理响应。如果将结果一起用于进一步处理中,那么结构是有意义的,如果不是这样,我倾向于将其作为单独的引用传递,除非要在复合语句中使用该函数:

x = divide( x, y, z ) + divide( a, b, c );

我经常选择在参数列表中通过引用传递“输出结构”,而不是通过返回新结构的复制开销进行传递(但这只是小事一桩)。

void divide(int dividend, int divisor, Answer &ans)

输出参数会造成混淆吗?作为参考发送的参数表明该值将要更改(与const参考相反)。合理的命名也可以消除混乱。


1
我认为这有点令人困惑。读取该代码的人看到“ divide(a,b,c);”。在他们查找签名之前,没有迹象表明c是过时的。但这是对非常量引用参数的普遍恐惧,而不是特定于此问题。
史蒂夫·杰索普

2

为什么要坚持具有多个返回值的函数?使用OOP,您可以使用提供常规函数的类,该函数具有单个返回值以及任何数量的其他“返回值”,如下所示。好处是,调用者可以选择查看额外的数据成员,但不需要这样做。对于复杂的数据库或网络调用,这是首选的方法,如果发生错误,可能需要大量附加的返回信息。

为了回答您的原始问题,此示例提供了一种返回商数的方法,这是大多数调用者可能需要的,并且,在方法调用之后,您可以将其余的作为数据成员。

class div{
   public:
      int remainder;

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

1
我认为在某些情况下效率低下。例如,您有一个for循环,它会生成多个返回值。如果将这些值拆分为单独的函数,则需要为每个值循环运行一次。
jiggunjer 2015年

1
@jiggunjer您可以运行一次循环,并将几个返回值存储在单独的类数据成员中。这强调了OOP概念的灵活性。
罗兰


1

对于从函数返回多个值的通用系统,Boost元组将是我的首选。

可能的例子:

include "boost/tuple/tuple.hpp"

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

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

1

我们可以声明函数,使其返回结构类型的用户定义变量或指向它的指针。通过结构的属性,我们知道C中的结构可以容纳多个非对称类型的值(即,一个int变量,四个char变量,两个float变量等等)


1

如果只是一些返回值,我只是通过引用来完成,但是对于更复杂的类型,您也可以像这样:

static struct SomeReturnType {int a,b,c; string str;} SomeFunction()
{
  return {1,2,3,string("hello world")}; // make sure you return values in the right order!
}

如果仅是临时返回类型,请使用“静态”将返回类型的范围限制为此编译单元。

 SomeReturnType st = SomeFunction();
 cout << "a "   << st.a << endl;
 cout << "b "   << st.b << endl;
 cout << "c "   << st.c << endl;
 cout << "str " << st.str << endl;

绝对不是最漂亮的方法,但它会起作用。


-2

这是此类问题解决方案的完整示例

#include <bits/stdc++.h>
using namespace std;
pair<int,int> solve(int brr[],int n)
{
    sort(brr,brr+n);

    return {brr[0],brr[n-1]};
}

int main()
{
    int n;
    cin >> n;
    int arr[n];
    for(int i=0; i<n; i++)
    {
        cin >> arr[i];
    }

    pair<int,int> o=solve(arr,n);
    cout << o.first << " " << o.second << endl;

    return 0;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.