从C++函数返回多个值

Fre*_*son 219 c++

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

void divide(int dividend, int divisor, int& quotient, int& remainder);
Run Code Online (Sandbox Code Playgroud)

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

int divide(int dividend, int divisor, int& remainder);
Run Code Online (Sandbox Code Playgroud)

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

struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);
Run Code Online (Sandbox Code Playgroud)

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

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

Rob*_*Rob 195

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

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

  • 在C++ 11中,您可以使用`std :: tuple`. (45认同)
  • 如果你想接受一个函数的多个值,一个方便的方法是使用`std :: tie` http://stackoverflow.com/a/2573822/502144 (13认同)
  • 如果您要使用元组,为什么不将它们用于成对.为什么有特例? (12认同)
  • +1元组.请记住在结构中返回的大对象与通过引用传递的性能后果. (11认同)
  • Fred,是的boost :: tuple可以做到:) (4认同)
  • @MooingDuck他们在C++ 17中更加出色:`auto&[a,b] = func();` (3认同)
  • Ferruccio:Boost并不是一个轻量级的依赖.如果你的项目还没有依赖它,多次返回值的`boost :: tuple`对于拖动Boost来说并不是一个好例子.(这来自于花了一个多小时在Windows上摆弄Boost特性的人和AIX;看起来像Linux上的"永远存在"依赖关系是其他地方严重头痛的根源.使用它,但要注意它.) (2认同)

pep*_*ico 151

在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;
}
Run Code Online (Sandbox Code Playgroud)

在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;
}
Run Code Online (Sandbox Code Playgroud)

或结构:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • @UchiaItachi同样关注函数参数,您可以给它们命名,但语言甚至不强制执行,并且参数名称在读取时在调用站点没有值.另外,在一次返回时,你只有一个类型,但是名字也可能有用,元组你只需要加倍问题,所以imo,语言只是缺乏以多种方式自我记录,而不仅仅是这个. (6认同)
  • 我对返回元组的函数有一个顾虑.假设上面的函数原型在标题中,那么如何在不理解函数定义的情况下知道第一个和第二个返回值是什么意思?商 - 余数或余数 - 商. (4认同)
  • @Slava 您不能在函数签名处定义类型,您必须在外部声明类型并将其用作返回类型,就像通常所做的那样(只需将 `struct` 行移到函数体外并替换 `auto ` 函数返回带有 `result`。 (2认同)
  • @pepper_chico如果想把`divide`的函数定义放到一个单独的cpp文件中怎么办?我得到错误`错误:在扣除'auto'之前使用'auto divide(int,int)'.我该如何解决这个问题? (2认同)

Fre*_*son 120

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

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

我对这对/元组技术也有一些保留意见.主要是,返回值通常没有自然顺序.代码的读者如何知道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;
Run Code Online (Sandbox Code Playgroud)

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

与此相比:

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;
Run Code Online (Sandbox Code Playgroud)

我认为这更清楚了.

所以我认为我的第一选择是结构技术.在某些情况下,对/元组的想法可能是一个很好的解决方案.我想尽可能避免返回参数.

  • +1.一个函数应该返回*one*"thing",而不是一个以某种方式排序的"tuple of things". (5认同)
  • 大约15年前,当我们发现提升时,我们使用了元组,因为它非常方便.加班我们在可读性方面遇到了不利,特别是对于具有相同类型的元组(例如元组<double,double>;哪一个是哪个).所以最近我们更习惯于引入一个小的POD结构,其中至少成员变量的名称表明一些合理的东西. (4认同)
  • @anton_rh:C++ 14允许使用`auto`返回本地类型,因此`auto result = my_func();`很容易获得. (2认同)

Jam*_*ran 23

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
Run Code Online (Sandbox Code Playgroud)

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

  • 也不是自我记录.你还记得哪个x86寄存器是DIV的剩余部分吗? (5认同)
  • 这对我的简单例子有用.但是,一般情况下,返回的值可能超过两个. (3认同)
  • @Mark - 我同意位置解决方案的可维护性较低。您可能会遇到“置换和挡板”问题。 (2认同)

Ste*_*son 16

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

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


Bil*_*l K 12

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

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

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

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

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


Pio*_*ski 12

C++17,使用std::make_tuple结构化绑定并尽可能auto

#include <tuple>

#include <string>
#include <cstring>

auto func() {
    // ...
    return std::make_tuple(1, 2.2, std::string("str"), "cstr");
}

int main() {
    auto [i, f, s, cs] = func();
    return i + f + s.length() + strlen(cs);
}
Run Code Online (Sandbox Code Playgroud)

这样-O1可以完全优化: https: //godbolt.org/z/133rT9Pcq
-O3只需要优化 std::string: https: //godbolt.org/z/Mqbez73Kf

在这里: https: //godbolt.org/z/WWKvE3osv您可以看到 GCC 将所有返回值存储在单个内存块中 ( rdi+N),POD风格,证明没有性能损失。


Jon*_*ler 10

已有先例返回结构在C(因此C++)标准与div,ldiv(并且,在C99,lldiv从)函数<stdlib.h>(或<cstdlib>).

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

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

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


Joh*_*erg 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();
}
Run Code Online (Sandbox Code Playgroud)

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

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


Yak*_*ont 10

有很多方法可以返回多个参数。我会用力过猛。

使用参考参数:

void foo( int& result, int& other_result );
Run Code Online (Sandbox Code Playgroud)

使用指针参数:

void foo( int* result, int* other_result );
Run Code Online (Sandbox Code Playgroud)

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

写一个模板并使用它:

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)...); }
};
Run Code Online (Sandbox Code Playgroud)

那么我们可以这样做:

void foo( out<int> result, out<int> other_result )
Run Code Online (Sandbox Code Playgroud)

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

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

我们可以返回一个结构:

struct foo_r { int result; int other_result; };
foo_r foo();
Run Code Online (Sandbox Code Playgroud)

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

auto&&[result, other_result]=foo();
Run Code Online (Sandbox Code Playgroud)

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

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

std::tuple<int, int> foo();
Run Code Online (Sandbox Code Playgroud)

其缺点是参数未命名。这允许

auto&&[result, other_result]=foo();
Run Code Online (Sandbox Code Playgroud)

以及。在之前,我们可以改为:

int result, other_result;
std::tie(result, other_result) = foo();
Run Code Online (Sandbox Code Playgroud)

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

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

void foo( std::function<void(int result, int other_result)> );
Run Code Online (Sandbox Code Playgroud)

现在调用者做:

foo( [&](int result, int other_result) {
  /* code */
} );
Run Code Online (Sandbox Code Playgroud)

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

void get_all_values( std::function<void(int)> value )
Run Code Online (Sandbox Code Playgroud)

value回调可以被称为当500次你get_all_values( [&](int value){} )

对于纯粹的精神错乱,您甚至可以在延续上使用延续。

void foo( std::function<void(int, std::function<void(int)>)> result );
Run Code Online (Sandbox Code Playgroud)

其用途如下:

foo( [&](int result, auto&& other){ other([&](int other){
  /* code */
}) });
Run Code Online (Sandbox Code Playgroud)

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

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

void foo( std::function< void(span<int>) > results )
Run Code Online (Sandbox Code Playgroud)

在这里,我们用一个结果范围调用回调。我们甚至可以反复这样做。

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

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
}
Run Code Online (Sandbox Code Playgroud)

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

另一种解决方案是:

std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
Run Code Online (Sandbox Code Playgroud)

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

foo(7)([&](int result, int other_result){ /* code */ });
Run Code Online (Sandbox Code Playgroud)

这通过使用单独的括号将输出参数与输入参数分开。

使用variant协程,您可以制作foo返回类型变体(或仅返回类型)的生成器。语法还没有固定,所以我不会举例。

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

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();
Run Code Online (Sandbox Code Playgroud)

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

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

foo( int_source )( int_dest1, int_dest2 );
Run Code Online (Sandbox Code Playgroud)

然后这段代码不会任何事情,直到int_source有整数来提供它。当它这样做时,int_dest1int_dest2开始接收结果。

  • 这个答案比其他答案包含更多信息!特别是有关返回元组和结构的函数的“auto&amp;&amp;[result, other_result]=foo();”的信息。谢谢! (2认同)
  • 我很欣赏这个详尽的答案,特别是因为我仍然坚持使用 C++11,因此无法使用其他人提出的一些更现代的解决方案。 (2认同)

Mic*_*hel 5

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

  1. 如果您稍后决定要返回更多信息,那么它是不灵活的;
  2. 从标题中的函数声明中返回的内容以及以什么顺序返回的内容并不十分清楚.

对于使用您的函数的任何人来说,返回具有自记录成员变量名称的结构可能不会更容易出错.把我的同事戴上帽子,你的divide_result结构很容易让我,你的功能的潜在用户,2秒后立即理解.使用输出参数或神秘对和元组进行混乱会花费更多时间来阅读并且可能使用不当.甚至在使用该函数几次后,我仍然不记得参数的正确顺序.


PRA*_*AND 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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 使用 C++14,您还可以在“cal”上使用“auto”返回类型,使之更加简洁。(海事组织)。 (4认同)

Xia*_* Hu 5

以下是有关此主题的“核心指南”(由 Bjarne Stroustrup 和 Herb Sutter 撰写)的链接。

\n

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

\n

部分报价:

\n
\n

F.21:要返回多个 \xe2\x80\x9cout\xe2\x80\x9d 值,最好返回结构体或元组

\n

原因返回值是自记录为 \xe2\x80\x9coutput-only\xe2\x80\x9d 值。请注意,按照使用元组(包括对)的约定,C++ 确实具有多个返回值,可能在调用站点处使用绑定或结构化绑定 (C++17) 带来额外的便利。更喜欢使用返回值具有语义的命名结构。否则,无名元组在通用代码中很有用。

\n
\n