Возврат нескольких значений из функции 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 ]

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

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

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

4 голосов
/ 16 октября 2018

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

Использовать справочные параметры:

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.

Опять же со значениями Uniforn, мы можем сделать это:

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 начнут получать результаты.

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

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

2 голосов
/ 21 ноября 2014

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

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

class div{
   public:
      int remainder;

      int quotient(int dividend, int divisor){
         remainder = ...;
         return ...;
      }
};
2 голосов
/ 26 ноября 2008

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

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

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

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

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

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

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

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

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

2 голосов
/ 16 ноября 2017

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

int divide(int a,int b,int quo,int &rem)
1 голос
/ 21 мая 2019

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

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;

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

1 голос
/ 26 ноября 2008

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

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

include "boost/tuple/tuple.hpp"

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

{
  return make_tuple(dividend / divisor,dividend % divisor )
}
1 голос
/ 20 октября 2015

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

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