Возврат нескольких значений из функции C ++ - PullRequest
212 голосов
/ 26 ноября 2008

Есть ли предпочтительный способ вернуть несколько значений из функции 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);

Является ли один из этих способов вообще предпочтительным, или есть другие предложения?

Редактировать: В реальном коде может быть более двух результатов. Они также могут быть разных типов.

Ответы [ 20 ]

183 голосов
/ 26 ноября 2008

Для возврата двух значений я использую std::pair (обычно typedef'd). Вы должны взглянуть на boost::tuple (в C ++ 11 и новее, есть std::tuple) для более чем двух возвращаемых результатов.

С введением структурированного связывания в C ++ 17 возвращение std::tuple, вероятно, должно стать общепринятым стандартом.

140 голосов
/ 13 мая 2013

В 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;
}
117 голосов
/ 26 ноября 2008

Лично мне обычно не нравятся возвращаемые параметры по ряду причин:

  • при вызове не всегда очевидно, какие параметры являются входными, а какие выходными
  • Обычно вам нужно создать локальную переменную, чтобы перехватить результат, в то время как возвращаемые значения могут использоваться встроенными (что может или не может быть хорошей идеей, но по крайней мере у вас есть опция)
  • мне кажется чище иметь функцию "in door" и "out door" - все входы идут сюда, все выходы выходят туда
  • Мне нравится, чтобы мои списки аргументов были как можно короче

У меня также есть некоторые сомнения относительно техники пар / кортежей. В основном, часто нет естественного порядка возвращаемых значений. Как читатель кода узнает, является ли 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;

Мне кажется, это понятнее.

Так что я думаю, что мой первый выбор в целом - это техника структуры. Идея пары / кортежа, вероятно, является отличным решением в некоторых случаях. Я хотел бы избежать возвращаемых параметров, когда это возможно.

22 голосов
/ 26 ноября 2008
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 - это, по сути, ваше структурное решение, но оно уже определено для вас и готово для адаптации к любым двум типам данных.

15 голосов
/ 26 ноября 2008

Это полностью зависит от фактической функции и значения нескольких значений, а также их размеров:

  • Если они связаны, как в вашем примере дроби, то я бы пошел с экземпляром структуры или класса.
  • Если они на самом деле не связаны и не могут быть сгруппированы в класс / структуру, то, возможно, вам следует реорганизовать ваш метод на две части.
  • В зависимости от размера возвращаемых значений в памяти, вы можете захотеть вернуть указатель на экземпляр класса или структуру или использовать ссылочные параметры.
12 голосов
/ 26 ноября 2008

OO-решение для этого - создать класс отношений. Это не потребовало бы никакого дополнительного кода (сэкономило бы немного), было бы значительно чище / яснее, и дало бы вам некоторые дополнительные рефакторинги, позволяющие вам очистить код и вне этого класса.

На самом деле я думаю, что кто-то порекомендовал вернуть структуру, которая достаточно близка, но скрывает намерение, что это должен быть полностью продуманный класс с конструктором и несколькими методами, фактически «метод», который вы изначально упоминали ( как возвращающий пару), скорее всего, должен быть членом этого класса, возвращая свой экземпляр.

Я знаю, что ваш пример был просто "Примером", но факт в том, что если ваша функция не выполняет больше, чем любая другая функция, если вы хотите, чтобы она возвращала несколько значений, вы почти наверняка пропускаете объект.

Не бойтесь создавать эти крошечные классы для выполнения небольших кусочков работы - это волшебство ОО - вы заканчиваете тем, что разбиваете его на части, пока каждый метод не станет очень маленьким и простым, а каждый класс - маленьким и понятным.

Еще одна вещь, которая должна была указывать на то, что что-то не так: в OO у вас практически нет данных - OO не предназначена для передачи данных, класс должен управлять своими собственными данными внутри себя, любой передачей данных ( включая средства доступа) - это признак того, что вам может понадобиться что-то переосмыслить ..

10 голосов
/ 26 ноября 2008

Прецедент для возврата структур в стандарте C (и, следовательно, C ++) с функциями div, ldiv (и, в C99, lldiv) из <stdlib.h> (или <cstdlib>).

«Смесь возвращаемого значения и возвращаемых параметров» обычно наименее чистая.

Наличие функции, возвращающей статус и возвращающей данные через возвращаемые параметры, имеет смысл в C; это менее очевидно в C ++, где вы могли бы вместо этого использовать исключения для передачи информации об ошибках.

Если существует более двух возвращаемых значений, то лучше всего подходит механизм, подобный структуре.

10 голосов
/ 22 июля 2016

С 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 struct variadic. Подробнее:

Возвращение агрегатных переменных (структура) и синтаксиса для вариабельного шаблона C ++ 17 «Руководство по выводу конструкции»

5 голосов
/ 26 ноября 2008

Используйте структуру или класс для возвращаемого значения. Использование std::pair может пока работать, но

  1. это негибко, если вы решите позже, что хотите получить больше информации;
  2. из объявления функции в заголовке не очень понятно, что возвращается и в каком порядке.

Возвращение структуры с самодокументируемыми именами переменных-членов, вероятно, будет менее подвержено ошибкам для любого, кто использует вашу функцию. На мгновение надев мою коллегу, я, потенциальный пользователь вашей функции, легко могу понять вашу структуру divide_result через 2 секунды. Бездействие с выходными параметрами или таинственными парами и кортежами займет больше времени для чтения и может быть использовано неправильно. И, скорее всего, даже после использования функции несколько раз, я все еще не буду помнить правильный порядок аргументов.

4 голосов
/ 04 февраля 2015

Здесь я пишу программу, которая возвращает несколько значений (более двух значений) в 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 может возвращать более двух значений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...