从 C++ 函数返回多个值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/321068/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Returning multiple values from a C++ function
提问by Fred Larson
Is there a preferred way to return multiple values from a C++ function? For example, imagine a function that divides two integers and returns both the quotient and the remainder. One way I commonly see is to use reference parameters:
是否有从 C++ 函数返回多个值的首选方法?例如,假设一个函数将两个整数相除并返回商和余数。我经常看到的一种方法是使用引用参数:
void divide(int dividend, int divisor, int& quotient, int& remainder);
A variation is to return one value and pass the other through a reference parameter:
一种变体是返回一个值并通过引用参数传递另一个值:
int divide(int dividend, int divisor, int& remainder);
Another way would be to declare a struct to contain all of the results and return that:
另一种方法是声明一个结构来包含所有结果并返回:
struct divide_result {
int quotient;
int remainder;
};
divide_result divide(int dividend, int divisor);
Is one of these ways generally preferred, or are there other suggestions?
通常首选这些方式之一,还是有其他建议?
Edit: In the real-world code, there may be more than two results. They may also be of different types.
编辑:在现实世界的代码中,可能会有两个以上的结果。它们也可能是不同类型的。
采纳答案by Rob
For returning two values I use a std::pair
(usually typedef'd). You should look at boost::tuple
(in C++11 and newer, there's std::tuple
) for more than two return results.
为了返回两个值,我使用了一个std::pair
(通常是 typedef'd)。您应该查看boost::tuple
(在 C++11 和更新版本中,有std::tuple
)两个以上的返回结果。
With introduction of structured binding in C++ 17, returning std::tuple
should probably become accepted standard.
随着 C++ 17 中结构化绑定的引入,返回std::tuple
可能应该成为公认的标准。
回答by pepper_chico
In C++11 you can:
在 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;
}
In C++17:
在 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;
}
or with structs:
或使用结构:
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;
}
回答by Fred Larson
Personally, I generally dislike return parameters for a number of reasons:
就个人而言,我通常不喜欢返回参数有以下几个原因:
- it is not always obvious in the invocation which parameters are ins and which are outs
- you generally have to create a local variable to catch the result, while return values can be used inline (which may or may not be a good idea, but at least you have the option)
- it seems cleaner to me to have an "in door" and an "out door" to a function -- all the inputs go in here, all the outputs come out there
- I like to keep my argument lists as short as possible
- 在调用中哪些参数是 in 哪些是 out 并不总是很明显
- 您通常必须创建一个局部变量来捕获结果,而可以内联使用返回值(这可能是也可能不是一个好主意,但至少您可以选择)
- 对我来说,让一个函数有一个“indoor”和一个“outdoor”似乎更干净——所有的输入都在这里,所有的输出都在那里
- 我喜欢让我的参数列表尽可能短
I also have some reservations about the pair/tuple technique. Mainly, there is often no natural order to the return values. How is the reader of the code to know whether result.first is the quotient or the remainder? And the implementer could change the order, which would break existing code. This is especially insidious if the values are the same type so that no compiler error or warning would be generated. Actually, these arguments apply to return parameters as well.
我对配对/元组技术也有一些保留意见。主要是,返回值通常没有自然顺序。代码的读者如何知道 result.first 是商还是余数?实现者可以更改顺序,这会破坏现有代码。如果值是相同的类型,则这尤其阴险,因此不会生成编译器错误或警告。实际上,这些参数也适用于返回参数。
Here's another code example, this one a bit less trivial:
这是另一个代码示例,这个代码不那么简单:
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;
Does this print groundspeed and course, or course and groundspeed? It's not obvious.
这是打印地速和航向,还是航向和地速?这并不明显。
Compare to this:
与此相比:
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;
I think this is clearer.
我认为这更清楚。
So I think my first choice in general is the struct technique. The pair/tuple idea is likely a great solution in certain cases. I'd like to avoid the return parameters when possible.
所以我认为我的首选通常是结构技术。在某些情况下,对/元组的想法可能是一个很好的解决方案。我想尽可能避免返回参数。
回答by James Curran
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 is essentially your struct solution, but already defined for you, and ready to adapt to any two data types.
std::pair 本质上是您的 struct 解决方案,但已经为您定义,并准备好适应任何两种数据类型。
回答by Stewart Johnson
It's entirely dependent upon the actual function and the meaning of the multiple values, and their sizes:
它完全取决于实际功能和多个值的含义及其大小:
- If they're related as in your fraction example, then I'd go with a struct or class instance.
- If they're not really related and can't be grouped into a class/struct then perhaps you should refactor your method into two.
- Depending upon the in-memory size of the values you're returning, you may want to return a pointer to a class instance or struct, or use reference parameters.
- 如果它们与您的分数示例相关,那么我会使用结构或类实例。
- 如果它们不是真正相关并且不能分组到一个类/结构中,那么也许您应该将您的方法重构为两个。
- 根据您返回的值在内存中的大小,您可能想要返回一个指向类实例或结构的指针,或者使用引用参数。
回答by Bill K
The OO solution for this is to create a ratio class. It wouldn't take any extra code (would save some), would be significantly cleaner/clearer, and would give you some extra refactorings letting you clean up code outside this class as well.
面向对象的解决方案是创建一个比率类。它不需要任何额外的代码(会节省一些),会更清晰/更清晰,并且会给你一些额外的重构,让你也可以清理这个类之外的代码。
Actually I think someone recommended returning a structure, which is close enough but hides the intent that this needs to be a fully thought-out class with constructor and a few methods, in fact, the "method" that you originally mentioned (as returning the pair) should most likely be a member of this class returning an instance of itself.
实际上,我认为有人建议返回一个结构,它足够接近,但隐藏了这需要是一个带有构造函数和一些方法的经过深思熟虑的类的意图,实际上,您最初提到的“方法”(作为返回pair) 应该最有可能是这个类的成员,返回自身的一个实例。
I know your example was just an "Example", but the fact is that unless your function is doing way more than any function should be doing, if you want it to return multiple values you are almost certainly missing an object.
我知道你的例子只是一个“例子”,但事实是,除非你的函数做的比任何函数都多,否则如果你想让它返回多个值,你几乎肯定会丢失一个对象。
Don't be afraid to create these tiny classes to do little pieces of work--that's the magic of OO--you end up breaking it down until every method is very small and simple and every class small and understandable.
不要害怕创建这些小类来完成一些小的工作——这就是面向对象的魔力——你最终会分解它,直到每个方法都非常小而简单,每个类都小而易于理解。
Another thing that should have been an indicator that something was wrong: in OO you have essentially no data--OO isn't about passing around data, a class needs to manage and manipulate it's own data internally, any data passing (including accessors) is a sign that you may need to rethink something..
另一件事应该表明出现问题:在 OO 中,您基本上没有数据——OO 不是关于传递数据,一个类需要在内部管理和操作它自己的数据,任何数据传递(包括访问器)是一个迹象,表明你可能需要重新考虑一些事情..
回答by Jonathan Leffler
There is precedent for returning structures in the C (and hence C++) standard with the div
, ldiv
(and, in C99, lldiv
) functions from <stdlib.h>
(or <cstdlib>
).
在 C(以及因此 C++)标准中使用div
, ldiv
(并且,在 C99 中,lldiv
)函数从<stdlib.h>
(or <cstdlib>
)返回结构有先例。
The 'mix of return value and return parameters' is usually the least clean.
“返回值和返回参数的混合”通常是最不干净的。
Having a function return a status and return data via return parameters is sensible in C; it is less obviously sensible in C++ where you could use exceptions to relay failure information instead.
让函数返回状态并通过返回参数返回数据在 C 中是明智的;在 C++ 中,您可以使用异常来代替传递失败信息,这显然不太明智。
If there are more than two return values, then a structure-like mechanism is probably best.
如果有两个以上的返回值,那么类似结构的机制可能是最好的。
回答by Johan Lundberg
With C++17 you can also return one ore more unmovable/uncopyable values(in certain cases). The possibility to return unmovable types come via the new guaranteed return value optimization, and it composes nicely with aggregates, and what can be called templated constructors.
使用 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();
}
The pretty thing about this is that it is guaranteed to not cause anycopying or moving. You can make the example many
struct variadic too. More details:
这样做的好处是它保证不会导致任何复制或移动。您也可以使示例many
结构可变参数。更多细节:
回答by Yakk - Adam Nevraumont
There are a bunch of ways to return multiple parameters. I'm going to be exhastive.
有很多方法可以返回多个参数。我会用力过猛。
Use reference parameters:
使用参考参数:
void foo( int& result, int& other_result );
use pointer parameters:
使用指针参数:
void foo( int* result, int* other_result );
which has the advantage that you have to do a &
at the call-site, possibly alerting people it is an out-parameter.
这样做的好处是你必须&
在呼叫站点做一个,可能会提醒人们它是一个外参数。
Write a template and use it:
写一个模板并使用它:
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)...); }
};
then we can do:
那么我们可以这样做:
void foo( out<int> result, out<int> other_result )
and all is good. foo
is no longer able to read any value passed in as a bonus.
一切都很好。 foo
不再能够读取作为奖励传入的任何值。
Other ways of defining a spot you can put data can be used to construct out
. A callback to emplace things somewhere, for example.
可以使用其他定义可以放置数据的点的方法来构建out
. 例如,将事物放置在某处的回调。
We can return a structure:
我们可以返回一个结构:
struct foo_r { int result; int other_result; };
foo_r foo();
whick works ok in every version of C++, and in c++17this also permits:
whick 在 C++ 的每个版本中都可以正常工作,在C++17 中这也允许:
auto&&[result, other_result]=foo();
at zero cost. Parameters can even not even be moved thanks to guaranteed elision.
以零成本。由于有保证的省略,参数甚至不能移动。
We could return a std::tuple
:
我们可以返回一个std::tuple
:
std::tuple<int, int> foo();
which has the downside that parameters are not named. This permits the c++17:
其缺点是参数未命名。这允许c++17:
auto&&[result, other_result]=foo();
as well. Prior to c++17we can instead do:
以及。在c++17之前,我们可以改为:
int result, other_result;
std::tie(result, other_result) = foo();
which is just a bit more awkward. Guaranteed elision doesn't work here, however.
这只是有点尴尬。然而,保证省略在这里不起作用。
Going into stranger territory (and this is after out<>
!), we can use continuation passing style:
进入陌生领域(这是在out<>
!)之后,我们可以使用延续传递风格:
void foo( std::function<void(int result, int other_result)> );
and now callers do:
现在调用者做:
foo( [&](int result, int other_result) {
/* code */
} );
a benefit of this style is you can return an arbitrary number of values (with uniform type) without having to manage memory:
这种风格的一个好处是你可以返回任意数量的值(具有统一类型)而无需管理内存:
void get_all_values( std::function<void(int)> value )
the value
callback could be called 500 times when you get_all_values( [&](int value){} )
.
该value
回调可以被称为当500次你get_all_values( [&](int value){} )
。
For pure insanity, you could even use a continuation on the continuation.
对于纯粹的精神错乱,您甚至可以在延续上使用延续。
void foo( std::function<void(int, std::function<void(int)>)> result );
whose use looks like:
其用途如下:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
which would permit many-one relationships between result
and other
.
这将允许result
和之间的多一关系other
。
Again with uniforn values, we can do this:
再次使用统一值,我们可以这样做:
void foo( std::function< void(span<int>) > results )
here, we call the callback with a span of results. We can even do this repeatedly.
在这里,我们用一个结果范围调用回调。我们甚至可以反复这样做。
Using this, you can have a function that efficiently passes megabytes of data without doing any allocation off the stack.
使用它,您可以拥有一个有效地传递兆字节数据的函数,而无需在堆栈外进行任何分配。
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
}
Now, std::function
is a bit heavy for this, as we would be doing this in zero-overhead no-allocation environments. So we'd want a function_view
that never allocates.
现在,std::function
对此有点沉重,因为我们将在零开销无分配环境中执行此操作。所以我们想要一个function_view
永远不会分配的。
Another solution is:
另一种解决方案是:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);
where instead of taking the callback and invoking it, foo
instead returns a function which takes the callback.
where 不是接受回调并调用它,foo
而是返回一个接受回调的函数。
foo(7)([&](int result, int other_result){ /* code */ }); this breaks the output parameters from the input parameters by having separate brackets.
foo(7)([&](int result, int other_result){ /* 代码 */ }); 这通过使用单独的括号将输出参数与输入参数分开。
With variant
and c++20coroutines, you could make foo
a generator of a variant of the return types (or just the return type). The syntax is not yet fixed, so I won't give examples.
使用variant
和c++20协程,您可以制作foo
返回类型变体(或仅返回类型)的生成器。语法还没有固定,所以我不会举例。
In the world of signals and slots, a function that exposes a set of signals:
在信号和槽的世界中,一个公开一组信号的函数:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
allows you to create a foo
that does work async and broadcasts the result when it is finished.
允许您创建一个foo
异步工作并在完成时广播结果。
Down this line we have a variety of pipeline techniques, where a function doesn't do something but rather arranges for data to be connected in some way, and the doing is relatively independant.
沿着这条线,我们有各种管道技术,其中一个函数不做某事,而是安排以某种方式连接数据,并且这些做是相对独立的。
foo( int_source )( int_dest1, int_dest2 );
then this code doesn't doanything until int_source
has integers to provide it. When it does, int_dest1
and int_dest2
start recieving the results.
然后这段代码不会做任何事情,直到int_source
有整数来提供它。当它这样做时,int_dest1
并int_dest2
开始接收结果。
回答by Michel
Use a struct or a class for the return value. Using std::pair
may work for now, but
使用结构或类作为返回值。使用std::pair
现在可能有效,但是
- it's inflexible if you decide later you want more info returned;
- it's not very clear from the function's declaration in the header what is being returned and in what order.
- 如果您稍后决定要返回更多信息,那将是不灵活的;
- 从头文件中函数的声明并不清楚返回什么以及以什么顺序返回。
Returning a structure with self-documenting member variable names will likely be less bug-prone for anyone using your function. Putting my coworker hat on for a moment, your divide_result
structure is easy for me, a potential user of your function, to immediately understand after 2 seconds. Messing around with ouput parameters or mysterious pairs and tuples would take more time to read through and may be used incorrectly. And most likely even after using the function a few times I still won't remember the correct order of the arguments.
对于使用您的函数的任何人来说,返回具有自记录成员变量名称的结构可能不太容易出错。暂时戴上我的同事帽子,您的divide_result
结构对于我(您的功能的潜在用户)来说很容易在 2 秒后立即理解。弄乱输出参数或神秘的对和元组会花费更多的时间来阅读并且可能会被错误地使用。很可能即使在多次使用该函数后,我仍然不记得参数的正确顺序。