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); |
这些方式中的一种通常是首选,还是有其他建议?
编辑:在现实世界的代码中,可能会有两个以上的结果。 它们也可以是不同类型的。
为了返回两个值,我使用
随着C ++ 17中结构化绑定的引入,返回
在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不是关于传递数据,类需要在内部管理和操纵它自己的数据,任何数据传递(包括访问器)是一个迹象,你可能需要重新思考一些东西..
使用
"返回值和返回参数的混合"通常是最不干净的。
让函数返回状态并通过返回参数返回数据在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(); } |
关于这一点的好处是保证不会导致任何复制或移动。您也可以使示例
返回C ++ 17可变参数模板构造演绎指南的可变参数聚合(struct)和语法
使用结构或类作为返回值。使用
对于使用您的函数的任何人来说,返回具有自记录成员变量名称的结构可能不会更容易出错。把我的同事帽子放了一会儿,你的
在这里,我正在编写一个在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 ) |
一切都很好。
定义可以放置数据的点的其他方法可用于构造
我们可以返回一个结构:
1 2 | struct foo_r { int result; int other_result; }; foo_r foo(); |
在每个版本的C ++中都可以正常工作,而在c ++ 17中,这也允许:
1 | auto&&[result, other_result]=foo(); |
零成本。由于保证省略,甚至不能移动参数。
我们可以返回
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(); |
这有点尴尬。但是,保证省略在这里不起作用。
进入陌生的领域(这是在
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 ) |
当
对于纯粹的精神错乱,你甚至可以继续使用延续。
1 | void foo( std::function<void(int, std::function<void(int)>)> result ); |
其用途如下:
1 2 3 | foo( [&](int result, auto&& other){ other([&](int other){ /* code */ }) }); |
这将允许
再次使用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 } |
现在,
另一种解决方案是
1 | std::function<void(std::function<void(int result, int other_result)>)> foo(int input); |
而不是采取回调并调用它,
foo(7)([&](int result,int other_result){/ * code * /});
这会通过使用单独的括号来打破输入参数的输出参数。
使用
在信号和插槽的世界中,一个暴露一组信号的功能:
1 2 3 4 | template<class...Args> struct broadcaster; broadcaster<int, int> foo(); |
允许您创建一个异步工作的
在这一行中,我们有各种管道技术,其中一个函数不做某事,而是安排数据以某种方式连接,并且这样做是相对独立的。
1 | foo( int_source )( int_dest1, int_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)倾向于使用引用参数以简化,因为很清楚谁分配以及它将如何在堆栈上查看,减少结构的扩散,并允许单独的返回值以获得成功。
"纯粹的"程序员更喜欢结构,假设它是函数值(就像这里的情况一样),而不是函数偶然触及的东西。如果你有一个更复杂的程序,或者有状态的东西,你可能会使用引用(假设你有理由不使用类)。
我会说没有首选的方法,这取决于你将如何处理响应。如果结果将在进一步处理中一起使用,那么结构是有意义的,如果不是,我倾向于作为单独的引用传递,除非该函数将在复合语句中使用:
我经常选择在参数列表中通过引用传递'out结构',而不是通过返回新结构的复制开销来传递(但这会让一些小东西冒汗)。
外出的参数令人困惑吗?作为参考发送的参数表明该值将发生变化(与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 ) } |