从C ++函数返回多个值


Returning multiple values from a C++ function

有没有从C ++函数返回多个值的首选方法? 例如,假设一个函数分割两个整数并返回商和余数。 我经常看到的一种方法是使用参考参数:

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

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

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

另一种方法是声明一个包含所有结果的结构并返回:

1
2
3
4
5
6
struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

这些方式中的一种通常是首选,还是有其他建议?

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


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

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


在C ++ 11中,您可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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;
}

或结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}


就个人而言,我通常不喜欢返回参数,原因如下:

  • 在调用中并不总是很明显哪些参数是ins而哪些是out
  • 你通常必须创建一个局部变量来捕获结果,而返回值可以内联使用(这可能是也可能不是一个好主意,但至少你有选择)
  • 对我来说似乎更清洁一个功能的"门"和"外门" - 所有输入都在这里,所有输出都出现在那里
  • 我想让我的论点清单尽可能短

我对这对/元组技术也有一些保留意见。主要是,返回值通常没有自然顺序。代码的读者如何知道result.first是商还是余数?并且实现者可以更改顺序,这将破坏现有代码。如果值是相同的类型,则这尤其隐蔽,因此不会生成编译器错误或警告。实际上,这些参数也适用于返回参数。

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

1
2
3
4
5
6
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;

这是打印地面速度和路线,还是路线和地面速度?这并不明显。

与此相比:

1
2
3
4
5
6
7
8
9
10
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
2
3
4
5
6
7
8
9
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本质上是你的结构解决方案,但已经为你定义,并且随时可以适应任何两种数据类型。


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

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


OO解决方案是创建比率等级。它不需要任何额外的代码(会节省一些),会更清晰/更清晰,并会给你一些额外的重构,让你清理这个类之外的代码。

实际上我认为有人建议返回一个结构,这个结构足够接近但隐藏了这个意图,它需要一个完全深思熟虑的类,带有构造函数和一些方法,实际上,你最初提到的"方法"(作为返回pair)应该很可能是这个类的成员返回自己的实例。

我知道你的例子只是一个"例子",但事实是除非你的函数比任何函数都要做的更多,如果你想要它返回多个值,你几乎肯定会丢失一个对象。

不要害怕创建这些小课程来完成一些小工作 - 这就是OO的魔力 - 你最终会将其分解,直到每个方法都非常小而且简单,每个类都小而易懂。

另一件事应该是一个错误的指示:在OO中你基本上没有数据 - OO不是关于传递数据,类需要在内部管理和操纵它自己的数据,任何数据传递(包括访问器)是一个迹象,你可能需要重新思考一些东西..


使用(或)中的divldiv(以及C99,lldiv)函数返回C(以及C ++)标准中的结构是先例。

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

让函数返回状态并通过返回参数返回数据在C中是明智的;在C ++中,您可以使用异常来转发故障信息。

如果有两个以上的返回值,那么类似结构的机制可能是最好的。


使用C ++ 17,您还可以返回一个或多个不可移动/不可复制的值(在某些情况下)。返回不可移动类型的可能性来自新的保证返回值优化,并且它与聚合以及可以称为模板化构造函数的组合很好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
}

关于这一点的好处是保证不会导致任何复制或移动。您也可以使示例many struct variadic。更多细节:

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


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

  • 如果您稍后决定要返回更多信息,那么它是不灵活的;
  • 从标题中的函数声明中返回的内容以及以什么顺序返回的内容并不十分清楚。
  • 对于使用您的函数的任何人来说,返回具有自记录成员变量名称的结构可能不会更容易出错。把我的同事帽子放了一会儿,你的divide_result结构很容易让我(你的功能的潜在用户)在2秒后立即理解。使用输出参数或神秘对和元组进行混乱会花费更多时间来阅读并且可能使用不当。甚至在使用该函数几次后,我仍然不记得参数的正确顺序。


    在这里,我正在编写一个在c ++中返回多个值(超过两个值)的程序。该程序可在c ++ 14(G ++ 4.9.2)中执行。程序就像一个计算器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #  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可以返回两个以上的值。


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

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


    有很多方法可以返回多个参数。我会变得筋疲力尽。

    使用参考参数:

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

    使用指针参数:

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

    这样做的好处是你必须在呼叫站点做一个&,可能会提醒人们它是一个超出参数的人。

    写一个模板并使用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    template<class T>
    struct out {
      std::function<void(T)> target;
      out(T* t):target([t](T&& in){ *t = 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>
      void emplace(Args&&...args) {
        target( T(std::forward<Args>(args)...) );
      }
      template<class X>
      void operator=(X&&x){ emplace(std::forward<X>(x)); }
      template<class...Args>
      void operator()(Args...&&args){ emplace(std::forward<Args>(args)...); }
    };

    然后我们可以这样做:

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

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

    定义可以放置数据的点的其他方法可用于构造out。例如,回调将某事放在某处。

    我们可以返回一个结构:

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

    在每个版本的C ++中都可以正常工作,而在c ++ 17中,这也允许:

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

    零成本。由于保证省略,甚至不能移动参数。

    我们可以返回std::tuple

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

    它的缺点是参数没有命名。这允许c ++ 17:

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

    同样。在c ++ 17之前,我们可以改为:

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

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

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

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

    现在来电者做:

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

    这种风格的好处是你可以返回任意数量的值(统一类型)而无需管理内存:

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

    get_all_values( [&](int value){} )时,value回调可被调用500次。

    对于纯粹的精神错乱,你甚至可以继续使用延续。

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

    其用途如下:

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

    这将允许resultother之间的多个关系。

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

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

    在这里,我们用一系列结果来调用回调。我们甚至可以反复这样做。

    使用它,您可以拥有一个有效传递兆字节数据的功能,而无需从堆栈中进行任何分配。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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

    另一种解决方案是

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

    而不是采取回调并调用它,foo而是返回一个接受回调的函数。

    foo(7)([&](int result,int other_result){/ * code * /});
    这会通过使用单独的括号来打破输入参数的输出参数。

    使用variant和c ++ 20协同程序,可以使foo成为返回类型变体的生成器(或仅返回类型)。语法尚未修复,所以我不举例。

    在信号和插槽的世界中,一个暴露一组信号的功能:

    1
    2
    3
    4
    template<class...Args>
    struct broadcaster;

    broadcaster<int, int> foo();

    允许您创建一个异步工作的foo,并在结束时广播结果。

    在这一行中,我们有各种管道技术,其中一个函数不做某事,而是安排数据以某种方式连接,并且这样做是相对独立的。

    1
    foo( int_source )( int_dest1, int_dest2 );

    然后,在int_source有整数提供它之前,此代码不会执行任何操作。如果是,int_dest1int_dest2开始收到结果。


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


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class div{
       public:
          int remainder;

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


    替代方案包括数组,生成器和控制反转,但这里没有一个是合适的。

    一些(例如历史Win32中的Microsoft)倾向于使用引用参数以简化,因为很清楚谁分配以及它将如何在堆栈上查看,减少结构的扩散,并允许单独的返回值以获得成功。

    "纯粹的"程序员更喜欢结构,假设它是函数值(就像这里的情况一样),而不是函数偶然触及的东西。如果你有一个更复杂的程序,或者有状态的东西,你可能会使用引用(假设你有理由不使用类)。


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

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

    我经常选择在参数列表中通过引用传递'out结构',而不是通过返回新结构的复制开销来传递(但这会让一些小东西冒汗)。

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

    外出的参数令人困惑吗?作为参考发送的参数表明该值将发生变化(与const引用相反)。明智的命名也可以消除混乱。


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

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


    我只是通过引用来做它,如果它只是几个返回值,但对于更复杂的类型,你也可以这样做:

    1
    2
    3
    4
    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!
    }

    如果它只是一个临时返回类型,则使用"static"将返回类型的范围限制为此编译单元。

    1
    2
    3
    4
    5
     SomeReturnType st = SomeFunction();
     cout <<"a"   << st.a << endl;
     cout <<"b"   << st.b << endl;
     cout <<"c"   << st.c << endl;
     cout <<"str" << st.str << endl;

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


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


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

    可能的例子:

    1
    2
    3
    4
    5
    6
    7
    include"boost/tuple/tuple.hpp"

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

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