有没有从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应该成为公认的标准.
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)
Fre*_*son 120
就个人而言,我通常不喜欢返回参数,原因如下:
我对这对/元组技术也有一些保留意见.主要是,返回值通常没有自然顺序.代码的读者如何知道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)
我认为这更清楚了.
所以我认为我的第一选择是结构技术.在某些情况下,对/元组的想法可能是一个很好的解决方案.我想尽可能避免返回参数.
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本质上是你的结构解决方案,但已经为你定义,并准备适应任何两种数据类型.
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++ 的每个版本中都可以正常工作,在C++17 中这也允许:
auto&&[result, other_result]=foo();
Run Code Online (Sandbox Code Playgroud)
以零成本。由于有保证的省略,参数甚至不能移动。
我们可以返回一个std::tuple:
std::tuple<int, int> foo();
Run Code Online (Sandbox Code Playgroud)
其缺点是参数未命名。这允许c++17:
auto&&[result, other_result]=foo();
Run Code Online (Sandbox Code Playgroud)
以及。在c++17之前,我们可以改为:
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和c++20协程,您可以制作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_dest1并int_dest2开始接收结果。
使用结构或类作为返回值.使用std::pair可能现在可以工作,但是
对于使用您的函数的任何人来说,返回具有自记录成员变量名称的结构可能不会更容易出错.把我的同事戴上帽子,你的divide_result结构很容易让我,你的功能的潜在用户,2秒后立即理解.使用输出参数或神秘对和元组进行混乱会花费更多时间来阅读并且可能使用不当.甚至在使用该函数几次后,我仍然不记得参数的正确顺序.
在这里,我正在编写一个用 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 可以返回两个以上的值。
以下是有关此主题的“核心指南”(由 Bjarne Stroustrup 和 Herb Sutter 撰写)的链接。
\nhttps://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-out-multi
\n部分报价:
\n\n\nF.21:要返回多个 \xe2\x80\x9cout\xe2\x80\x9d 值,最好返回结构体或元组
\n原因返回值是自记录为 \xe2\x80\x9coutput-only\xe2\x80\x9d 值。请注意,按照使用元组(包括对)的约定,C++ 确实具有多个返回值,可能在调用站点处使用绑定或结构化绑定 (C++17) 带来额外的便利。更喜欢使用返回值具有语义的命名结构。否则,无名元组在通用代码中很有用。
\n