Подтвердить что ты не робот

Возвращение нескольких значений из функции С++

Есть ли предпочтительный способ возврата нескольких значений из функции С++? Например, представьте себе функцию, которая делит два целых числа и возвращает как фактор, так и остаток. Один из способов, который я обычно вижу, - использовать ссылочные параметры:

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);

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

В С++ 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;
}

В С++ 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;
}

Ответ 3

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

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

У меня также есть некоторые оговорки в отношении метода пары/кортежа. Главным образом, часто нет естественного порядка для возвращаемых значений. Как читатель кода узнает, является ли результат. Первым является фактор или остаток? И разработчик мог бы изменить порядок, который нарушил бы существующий код. Это особенно коварно, если значения одного типа, так что никакая ошибка компилятора или предупреждение не будут сгенерированы. Фактически, эти аргументы применимы и к возвращаемым параметрам.

Вот еще один пример кода, это немного менее тривиально:

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;

Я думаю, что это яснее.

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

"Смешение возвращаемого значения и возвращаемых параметров" обычно является наименее чистым.

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

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

Ответ 8

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

Возвращение переменных агрегатов (struct) и синтаксиса для С++ 17 Variadic template "Руководство по выведению из строя"

Ответ 9

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

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

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

Ответ 10

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

Если вы вернетесь по ссылке, оптимизация вашей программы будет страдать

Ответ 11

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

Ответ 12

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

Ответ 13

Существует множество способов вернуть несколько параметров. Я собираюсь быть внимательным.

Используйте опорные параметры:

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){ *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)...); }
};

тогда мы можем сделать:

void foo( out<int> result, out<int> other_result )

и все хорошо. foo больше не может читать любые значения, переданные в качестве бонуса.

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

Мы можем вернуть структуру:

struct foo_r { int result; int other_result; };
foo_r foo();

whick работает нормально в каждой версии C++, а в это также позволяет:

auto&&[result, other_result]=foo();

по нулевой цене. Параметры даже не могут быть перемещены благодаря гарантированному выбору.

Мы могли бы вернуть std::tuple:

std::tuple<int, int> foo();

недостаток в том, что параметры не названы. Это позволяет :

auto&&[result, other_result]=foo();

также. До мы можем вместо этого сделать:

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

В мире сигналов и слотов, функция, которая выставляет набор сигналов:

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();

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

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

foo( int_source )( int_dest1, int_dest2 );

тогда этот код ничего не делает, пока int_source имеет целые числа для его предоставления. Когда это происходит, int_dest1 и int_dest2 начинают получать результаты.

Ответ 14

Альтернативы включают массивы, генераторы и инверсия элемента управления, но здесь не подходит.

Некоторые (например, Microsoft в историческом Win32) имеют тенденцию использовать ссылочные параметры для простоты, потому что они четко определяют, кто распределяет и как это будет выглядеть в стеке, уменьшает распространение структур и позволяет получить отдельное возвращаемое значение для успеха.

"Чистые" программисты предпочитают структуру, полагая, что это значение функции (как это имеет место здесь), а не что-то, что коснулось функции. Если у вас была более сложная процедура или что-то с состоянием, вы, вероятно, использовали бы ссылки (при условии, что у вас есть причина не использовать класс).

Ответ 15

Я бы сказал, что нет предпочтительного метода, все зависит от того, что вы собираетесь делать с ответом. Если результаты будут использоваться вместе в дальнейшей обработке, тогда структуры будут иметь смысл, если бы я не склонялся тогда к отдельным ссылкам, если функция не будет использоваться в составном заявлении:

x = divide( x, y, z ) + divide( a, b, c );

Я часто предпочитаю передавать "внешние структуры" по ссылке в списке параметров, вместо того, чтобы иметь пропуск накладные расходы на возврат новой структуры (но это потение небольшого материала).

void divide(int dividend, int divisor, Answer &ans)

Изъясняются ли параметры? Параметр, отправленный как ссылка, указывает на то, что значение изменится (в отличие от ссылки на константу). Разумное именование также устраняет путаницу.

Ответ 16

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

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

class div{
   public:
      int remainder;

      int quotient(int dividend, int divisor){
         remainder = ...;
         return ...;
      }
};

Ответ 17

вместо того, чтобы возвращать несколько значений, просто верните один из них и сделайте ссылку других в требуемой функции, например:

int divide(int a,int b,int quo,int &rem)

Ответ 18

Boost tuple будет моим предпочтительным выбором для обобщенной системы возврата более одного значения из функции.

Возможный пример:

include "boost/tuple/tuple.hpp"

tuple <int,int> divide( int dividend,int divisor ) 

{
  return make_tuple(dividend / divisor,dividend % divisor )
}

Ответ 19

Мы можем объявить такую ​​функцию, чтобы она возвращала определенную пользователем структуру типа или указатель на нее. И по свойству структуры мы знаем, что структура в C может содержать несколько значений асимметричных типов (т.е. Одна переменная int, четыре переменные char, две переменные float и т.д.)

Ответ 20

Я бы просто сделал это по ссылке, если это только несколько возвращаемых значений, но для более сложных типов вы также можете просто сделать это так:

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;

Это определенно не самый красивый способ сделать это, но это сработает.