Есть ли предпочтительный способ вернуть несколько значений из функции 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);
Является ли один из этих способов предпочтительным или есть другие предложения?
Обновлено: в реальном коде может быть более двух результатов. Также они могут быть разных типов.





Альтернативы включают массивы, генераторы и инверсия контроля, но ни один из них здесь не подходит.
Некоторые (например, Microsoft в исторической Win32), как правило, используют ссылочные параметры для простоты, потому что ясно, кто выделяет и как это будет выглядеть в стеке, уменьшает распространение структур и позволяет использовать отдельное возвращаемое значение для успеха.
«Чистые» программисты предпочитают структуру, предполагая, что она является значение функции (как в данном случае), а не то, что случайно затрагивается функцией. Если бы у вас была более сложная процедура или что-то с состоянием, вы, вероятно, использовали бы ссылки (при условии, что у вас есть причина не использовать класс).
Это полностью зависит от фактической функции и значения нескольких значений и их размеров:
Для возврата двух значений я использую std::pair (обычно typedef'd). Вы должны посмотреть на boost::tuple (в C++ 11 и новее есть std::tuple), чтобы получить более двух возвращаемых результатов.
С введением структурированного связывания в C++ 17 возврат std::tuple, вероятно, должен стать общепринятым стандартом.
+1 за кортеж. Помните о разветвлении производительности больших объектов, возвращаемых в структуре по сравнению с передачей по ссылке.
Если вы собираетесь использовать кортежи, почему бы не использовать их и для пар. Зачем нужен особый случай?
Может ли boost :: tuple обрабатывать разные типы в одном кортеже? Возможно, вы можете использовать с ним boost :: any.
Фред, да, boost :: tuple может это сделать :)
Прошло некоторое время, но я полагаю, что приму этот ответ как фаворит публики.
В C++ 11 вы можете использовать std::tuple.
Ферруччо: Boost - это не совсем легкая зависимость. Если ваш проект еще не зависит от него, boost::tuple для нескольких возвращаемых значений не совсем хороший случай для перетаскивания Boost (это исходит от человека, который много часов возился с особенностями Boost в Windows и AIX; что выглядит как будто зависимость от Linux "всегда рядом" является источником серьезных головных болей в других местах. Используйте ее, но помните об этом.)
Если вы хотите принять несколько значений от функции, удобный способ сделать это - использовать std::tiestackoverflow.com/a/2573822/502144
Я предпочитаю структуры в целом, но кортежи прекрасны из-за std::tie(var_a, var_b) = func_returning_tuple();
@MooingDuck они еще круче в C++ 17: auto &[a, b] = func();
@Ferruccio std::pair ведет себя как двухэлементный кортеж, а также имеет имена для элементов
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 - это, по сути, ваше структурное решение, но оно уже определено для вас и готово к адаптации к любым двум типам данных.
Это сработает для моего простого примера. Однако в общем случае может быть возвращено более двух значений.
Также не самодокументирующий. Вы можете вспомнить, какой регистр x86 остается для DIV?
@Mark - Я согласен с тем, что позиционные решения могут быть менее ремонтопригодными. Вы можете столкнуться с проблемой «переставить и сбить с толку».
Я склонен использовать out-val в таких функциях, потому что я придерживаюсь парадигмы функции, возвращающей коды успеха / ошибки, и мне нравится сохранять единообразие.
Я бы сказал, что нет предпочтительного метода, все зависит от того, что вы собираетесь делать с ответом. Если результаты будут использоваться вместе при дальнейшей обработке, тогда структуры имеют смысл, в противном случае я бы предпочел передать их как отдельные ссылки, если только функция не будет использоваться в составном операторе:
x = divide( x, y, z ) + divide( a, b, c );
Я часто предпочитаю передавать «структуры» по ссылке в списке параметров, вместо того, чтобы передавать через копирование накладных расходов, связанных с возвратом новой структуры (но это мелочь).
void divide(int dividend, int divisor, Answer &ans)
Не путают ли параметры? Параметр, отправленный как ссылка, предполагает, что значение будет изменено (в отличие от постоянной ссылки). Разумное именование также устраняет путаницу.
Я думаю, это немного сбивает с толку. Кто-то, читающий код, который его вызывает, видит «div (a, b, c);». Нет никаких указаний на то, что c - исключение, пока они не найдут подпись. Но это общий страх перед неконстантными ссылочными параметрами, а не конкретный вопрос.
Кортеж Boost был бы моим предпочтительным выбором для обобщенной системы, возвращающей более одного значения из функции.
Возможный пример:
include "boost/tuple/tuple.hpp"
tuple <int,int> divide( int dividend,int divisor )
{
return make_tuple(dividend / divisor,dividend % divisor )
}
Если ваша функция возвращает значение по ссылке, компилятор не может сохранить его в регистре при вызове других функций, потому что теоретически первая функция может сохранить адрес переданной ей переменной в глобально доступной переменной, а любые вызываемые впоследствии функции могут измените его, чтобы компилятор (1) сохранил значение из регистров обратно в память перед вызовом других функций и (2) повторно прочитал его, когда он снова понадобится из памяти после любого из таких вызовов.
Если вернуться по ссылке, то оптимизация вашей программы пострадает
Лично мне вообще не нравятся возвращаемые параметры по ряду причин:
У меня также есть некоторые оговорки по поводу техники пар / кортежей. Обычно возвращаемые значения не имеют естественного порядка. Как читатель кода узнает, является ли 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;
Думаю, это понятнее.
Так что я думаю, что мой первый выбор в целом - это техника структуры. Идея пары / кортежа, вероятно, будет отличным решением в определенных случаях. Я бы хотел по возможности избегать возвращаемых параметров.
Предложение объявить struct как Velocity - хорошее. Однако есть одна проблема - это загрязнение пространства имен. Я полагаю, что в C++ 11 struct может иметь длинное имя типа, и можно использовать auto result = calculateResultingVelocity(...).
+1. Функция должна возвращать один «вещь», а не как-то упорядоченный «кортеж вещей».
Я предпочитаю структуры, а не std :: pair / std :: tuples по причинам, описанным в этом ответе. Но мне также не нравится «загрязнение» пространства имен. Идеальным решением для меня было бы возвращение анонимной структуры типа struct { int a, b; } my_func();. Это могло быть использовано как это: auto result = my_func();. Но C++ этого не допускает: «новые типы не могут быть определены в возвращаемом типе». Поэтому мне нужно создать такие структуры, как struct my_func_result_t ...
@anton_rh: C++ 14 позволяет возвращать локальные типы с помощью auto, поэтому auto result = my_func(); легко получить.
@ildjarn Вроде работает: coliru.stacked-crooked.com/a/2a80e1007ff8804e. Но тело функции должно быть определено в файле заголовка.
Около 15 лет назад, когда мы открыли для себя boost, мы часто использовали кортежи, так как они очень удобны. Со временем мы столкнулись с недостатком удобочитаемости, особенно для кортежей одного типа (например, tuple <double, double>; which one is which). Так что в последнее время мы больше привыкли вводить небольшую структуру POD, где, по крайней мере, имя переменной-члена указывает на что-то разумное.
Прецедент возврата структур в стандарте C (и, следовательно, C++) с функциями div, ldiv (и, в C99, lldiv) из <stdlib.h> (или <cstdlib>).
«Сочетание возвращаемого значения и возвращаемых параметров» обычно наименее чистое.
В C разумно иметь функцию, возвращающую статус и возвращающие данные через возвращаемые параметры; это менее очевидно в C++, где вместо этого вы можете использовать исключения для передачи информации о сбоях.
Если возвращаемых значений больше двух, то, вероятно, лучше всего использовать структурный механизм.
Используйте структуру или класс для возвращаемого значения. Использование std::pair на данный момент может работать, но
Возврат структуры с самодокументированными именами переменных-членов, вероятно, будет менее подвержен ошибкам для тех, кто использует вашу функцию. Надев на мгновение шляпу коллеги, я, потенциальный пользователь вашей функции, легко могу понять вашу структуру divide_result через 2 секунды. Возникновение с выходными параметрами или загадочными парами и кортежами потребует больше времени для чтения и может использоваться неправильно. И, скорее всего, даже после использования функции несколько раз я все равно не запомню правильный порядок аргументов.
ОО-решение для этого - создать класс отношения. Это не потребовало бы никакого дополнительного кода (сэкономило бы часть), было бы значительно чище / яснее и дало бы вам несколько дополнительных рефакторингов, позволяющих вам очищать код за пределами этого класса.
На самом деле я думаю, что кто-то рекомендовал вернуть структуру, которая достаточно близка, но скрывает намерение, что это должен быть полностью продуманный класс с конструктором и несколькими методами, фактически, «метод», который вы первоначально упомянули (как возвращающий пара), скорее всего, должен быть членом этого класса, возвращающим свой экземпляр.
Я знаю, что ваш пример был просто «Примером», но факт в том, что если ваша функция не делает больше, чем должна делать любая функция, если вы хотите, чтобы она возвращала несколько значений, вам почти наверняка не хватает объекта.
Не бойтесь создавать эти крошечные классы для выполнения небольших частей работы - это магия объектно-ориентированного программирования - вы в конечном итоге разбиваете его до тех пор, пока каждый метод не станет очень маленьким и простым, а каждый класс небольшим и понятным.
Еще одна вещь, которая должна была быть индикатором того, что что-то не так: в 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;
}
У меня есть одна проблема с функциями, возвращающими кортежи. Скажем, приведенный выше прототип функции находится в заголовке, тогда как мне узнать, что означают первое и второе возвращенные значения, не понимая определения функции? частное-остаток или остаток-частное.
@UchiaItachi То же самое и с параметрами функций, вы можете давать им имена, но язык даже не требует этого, а имена параметров не имеют значения на месте вызова при чтении. Кроме того, при однократном возврате у вас просто есть тип, но наличие имени также может быть полезно, с кортежами вы просто удваиваете проблему, так что, я думаю, в языке просто не хватает самодокументирования несколькими способами, не только этим.
как бы выглядел последний пример, если бы я хотел явно указать тип возвращаемого значения div ()? Должен ли я затем определить результат где-то еще или я могу определить его прямо в спецификации возвращаемого типа?
@Slava, вы не можете определить тип прямо в сигнатуре функции, вам придется объявить тип снаружи и использовать его как возвращаемый тип, как это обычно делается (просто переместите строку struct за пределы тела функции и замените возврат функции auto на result.
@pepper_chico Что делать, если вы хотите поместить определение функции divide в отдельный файл cpp? Получаю ошибку error: use of ‘auto divide(int, int)’ before deduction of ‘auto’. Как мне решить эту проблему?
@Adriaan, вы решаете это, как я объяснял в моем предыдущем комментарии. У вас не может быть прототипов с возвратом auto в заголовках, тип возврата должен быть выведен из кода.
В первом примере вы можете использовать auto divide(int dividend, int divisor) { return std::pair{dividend / divisor, dividend % divisor}; }
Или даже auto [quotient, remainder] = div(14, 3) от #include <cstdlib>.
Почему вы настаиваете на функции с несколькими возвращаемыми значениями? С ООП вы можете использовать класс, предлагающий обычную функцию с одним возвращаемым значением и любым количеством дополнительных «возвращаемых значений», как показано ниже. Преимущество состоит в том, что вызывающий имеет выбор просмотра дополнительных элементов данных, но не обязан это делать. Это предпочтительный метод для сложных баз данных или сетевых вызовов, когда в случае возникновения ошибок может потребоваться много дополнительной информации о возврате.
Чтобы ответить на ваш исходный вопрос, в этом примере есть метод для возврата частного, который может понадобиться большинству вызывающих, и, кроме того, после вызова метода вы можете получить остаток как член данных.
class div{
public:
int remainder;
int quotient(int dividend, int divisor){
remainder = ...;
return ...;
}
};
Думаю, есть случаи, когда это неэффективно. Например. у вас есть один цикл for, который генерирует несколько возвращаемых значений. Если вы разделите эти значения на отдельные функции, вам нужно будет выполнить цикл один раз для каждого значения.
@jiggunjer Вы можете запустить цикл один раз и сохранить несколько возвращаемых значений в отдельных элементах данных класса. Это подчеркивает гибкость концепции ООП.
Здесь я пишу программу, которая возвращает несколько значений (более двух значений) на С ++. Эта программа исполняется на 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++ 14 вы также можете использовать возвращаемый тип auto на cal, чтобы сделать это еще чище. (ИМО).
Мы можем объявить функцию так, чтобы она возвращала определяемую пользователем переменную структурного типа или указатель на нее. А благодаря свойству структуры мы знаем, что структура в C может содержать несколько значений асимметричных типов (например, одну переменную int, четыре переменных типа char, две переменные с плавающей запятой и т. д.)
С 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 с переменным числом аргументов. Подробнее:
вместо того, чтобы возвращать несколько значений, просто верните одно из них и сделайте ссылку на другие в требуемой функции, например:
int divide(int a,int b,int quo,int &rem)
Я не упомянул об этом в самом вопросе? Также см. Мои возражения в мой ответ.
Есть несколько способов вернуть несколько параметров. Я буду исчерпывающим.
Используйте справочные параметры:
void foo( int& result, int& other_result );
использовать параметры указателя:
void foo( int* result, int* other_result );
который имеет то преимущество, что вы должны выполнить & на месте вызова, возможно, предупредив людей, что это выходной параметр.
Напишите шаблон и используйте его:
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++, а в C++ 17 это также позволяет:
auto&&[result, other_result]=foo();
по нулевой стоимости. Параметры даже нельзя перемещать благодаря гарантированному исключению.
Мы могли вернуть std::tuple:
std::tuple<int, int> foo();
у которого есть недостаток в том, что параметры не называются. Это позволяет C++ 17:
auto&&[result, other_result]=foo();
также. До C++ 17 вместо этого мы можем:
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 )
обратный вызов value может быть вызван 500 раз, когда вы используете get_all_values( [&](int value){} ).
Для чистого безумия можно даже использовать продолжение на продолжении.
void foo( std::function<void(int, std::function<void(int)>)> result );
чье использование выглядит так:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });
что позволило бы установить отношения "много-один" между result и other.
Опять же, с униформными ценностями мы можем сделать это:
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 и C++ 20 вы можете сделать foo генератором варианта возвращаемых типов (или просто возвращаемого типа). Синтаксис пока не исправлен, поэтому я не буду приводить примеры.
В мире сигналов и слотов функция, которая предоставляет набор сигналов:
template<class...Args>
struct broadcaster;
broadcaster<int, int> foo();
позволяет создать foo, который работает асинхронно и транслирует результат по завершении.
В этой строке у нас есть множество методов конвейера, в которых функция ничего не делает, а скорее организует соединение данных каким-либо образом, и это действие является относительно независимым.
foo( int_source )( int_dest1, int_dest2 );
тогда этот код ничего не делать, пока int_source не предоставит целые числа. Когда это происходит, int_dest1 и int_dest2 начинают получать результаты.
Этот ответ содержит больше информации, чем другие ответы! в частности, информация о auto&&[result, other_result]=foo(); для функций, возвращающих как кортежи, так и структуры. Спасибо!
Я ценю этот исчерпывающий ответ, тем более, что я все еще придерживаюсь C++ 11 и поэтому не могу использовать некоторые из более современных решений, которые предлагают другие люди.
Я бы просто сделал это по ссылке, если это всего несколько возвращаемых значений, но для более сложных типов вы также можете просто сделать это так:
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;
}
Мне нравится ваш ответ, и ваш последний пункт напомнил мне о том, что я только что прочитал, что передача по значению стала намного быстрее, в зависимости от обстоятельств, усложняющих задачу ... cpp-next.com/archive/2009/08/want-speed-pass-by-value