从c++函数返回多个值

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

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

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

492100 次浏览

可供选择的方法包括数组、发电机控制反转,但在这里都不合适。

有些(例如微软在历史上的Win32)倾向于使用引用参数来简化,因为它很清楚由谁分配以及它在堆栈上的外观,减少了结构的扩散,并允许成功的单独返回值。

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

这完全取决于实际函数和多个值的含义,以及它们的大小:

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

对于返回两个值,我使用std::pair(通常是类型定义的)。你应该查看boost::tuple(在c++ 11及更新版本中,有std::tuple),以找到两个以上的返回结果。

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

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本质上是你的结构解决方案,但是已经为你定义好了,并且可以适应任何两种数据类型。

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

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

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

我经常选择在参数列表中通过引用传递“结构”,而不是通过返回新结构的拷贝开销(但这是汗流浃背的小事)。

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

我们的参数令人困惑吗?作为引用发送的形参表明值将发生变化(与const引用相反)。合理的命名也可以消除混淆。

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

可能的例子:

include "boost/tuple/tuple.hpp"


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


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

如果你通过引用函数返回一个值,编译器不能存储在寄存器当调用其他函数,因为从理论上讲,第一个函数可以保存变量的地址传递给它的全局变量,和任何subsecuently称为功能可能改变它,所以编译器会有(1)将值从寄存器保存到内存之前调用其他函数和(2)重读的时候需要从内存后再任何这样的调用。

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

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

  • 在调用中,哪些参数是in,哪些是out并不总是很明显
  • 您通常必须创建一个局部变量来捕获结果,而返回值可以内联使用(这可能是也可能不是一个好主意,但至少您可以选择)
  • 在我看来,有一个“在家”的人似乎更干净。而“out door”;对于一个函数,所有的输入都在这里,所有的输出都在那里
  • 我喜欢让我的论点列表尽可能简短

我对pair/tuple技术也有一些保留意见。主要是,返回值通常没有自然的顺序。代码的读者如何知道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;

我想这更清楚了。

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

在C(以及c++)标准中,有从<stdlib.h>(或<cstdlib>)返回结构的divldiv(和,在C99中,lldiv)函数的先例。

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

让函数返回状态并通过返回参数返回数据在C中是合理的;在c++中,你可以使用异常来传递失败信息,这就不那么明显了。

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

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

  1. 如果你决定以后要返回更多信息,这是不灵活的;
  2. 从函数头中的声明来看,不太清楚返回的是什么以及返回的顺序。

对于使用您的函数的人来说,返回具有自记录成员变量名称的结构可能不太容易出错。暂时把我同事的帽子戴上,你的divide_result结构对我来说很容易,你的函数的潜在用户,在2秒后立即理解。乱动输出参数或神秘的对和元组将花费更多的时间来读取,并且可能被错误地使用。而且很有可能在使用这个函数几次之后,我仍然记不住参数的正确顺序。

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

实际上,我认为有人建议返回一个结构,这是足够接近,但隐藏的意图,这需要一个充分考虑的类与构造函数和一些方法,事实上,你最初提到的“方法”(作为返回对)应该最有可能是这个类的成员,返回自己的实例。

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

不要害怕创建这些小的类来做小的工作——这就是OO的神奇之处——你最终会分解它,直到每个方法都非常小而简单,每个类都非常小而可理解。

另一件事应该是一个错误的指示器:在OO中,你基本上没有数据——OO不是传递数据,一个类需要在内部管理和操作它自己的数据,任何数据传递(包括访问器)都是一个迹象,表明你可能需要重新考虑一些事情。

在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;
}

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

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

class div{
public:
int remainder;


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

在这里,我正在编写一个程序,在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可以返回两个以上的值。

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

使用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();
}

这样做的好处是它保证不会导致任何复制或移动。你也可以让例子many变成可变结构。更多的细节:

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

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

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

有很多方法可以返回多个参数。我会讲得很详尽。

使用参考参数:

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

使用指针参数:

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

它的优点是你必须在调用点做&,可能会提醒人们它是一个out形参。

写一个模板并使用它:

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 )

当你get_all_values( [&](int value){} ). value回调函数可以被调用500次。

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

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

其用途如下:

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

这将允许resultother之间存在多一关系。

同样使用统一的值,我们可以这样做:

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 result, int other_result){ /* code */ });

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

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

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

template<class...Args>
struct broadcaster;


broadcaster<int, int> foo();

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

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

foo( int_source )( int_dest1, int_dest2 );

那么这段代码不会任何东西,直到int_source有整数提供它。当它这样做时,int_dest1int_dest2开始接收结果。

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

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”将返回类型的范围限制在这个编译单元。

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

这绝对不是最漂亮的方法,但它是有效的。

快速回答:

#include <iostream>
using namespace std;


// different values of [operate] can return different number.
int yourFunction(int a, int b, int operate)
{
a = 1;
b = 2;


if (operate== 1)
{
return a;
}
else
{
return b;
}
}


int main()
{
int a, b;


a = yourFunction(a, b, 1); // get return 1
b = yourFunction(a, b, 2); // get return 2


return 0;
}

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);
}
使用-O1完全优化:https://godbolt.org/z/133rT9Pcq
-O3只需要优化std::string: https://godbolt.org/z/Mqbez73Kf

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

这里是“核心指南”的链接。(作者:Bjarne Stroustrup和Herb Sutter)。

< a href = " https://isocpp.github。io/CppCoreGuidelines/CppCoreGuidelines#Rf-out-multi" rel="nofollow noreferrer">https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-out-multi . cn

部分报价:

F.21:要返回多个“out”值,最好返回一个结构体或元组

原因返回值是自记录的“仅输出”值。注意,根据使用元组(包括pair)的惯例,c++确实有多个返回值,可能在调用点使用额外方便的tie或结构化绑定(c++ 17)。首选使用有返回值语义的命名结构。否则,无名元组在泛型代码中很有用。