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

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

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

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

23 ответа

Решение

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

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

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

или со структурами:

#include <tuple>

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

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

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

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

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

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

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

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

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

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

С 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 структура variadic тоже. Больше деталей:

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

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

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

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

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

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

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

С -O1это полностью оптимизирует: https://godbolt.org/z/133rT9Pcq
-O3нужен только для оптимизации std::string: https://godbolt.org/z/Mqbez73Kf

А здесь: https://godbolt.org/z/WWKvE3osv вы можете увидеть, как GCC хранит все возвращаемые значения, упакованные вместе в один блок памяти ( rdi+N), в стиле POD, что доказывает отсутствие снижения производительности.

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

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

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

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

Вот ссылка на «основные принципы» (от Бьярна Страуструпа и Херба Саттера) по этой теме.

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-out-multi

Частичная цитата:

F.21: Чтобы вернуть несколько «выходных» значений, предпочтительнее возвращать структуру или кортеж.

Причина Возвращаемое значение самодокументируется как значение «только для вывода». Обратите внимание, что C++ имеет несколько возвращаемых значений по соглашению об использовании кортежа (включая пару), возможно, с дополнительным удобством связывания или структурированных привязок (C++17) на месте вызова. Лучше использовать именованную структуру, в которой есть семантика возвращаемого значения. В противном случае безымянный кортеж полезен в универсальном коде.

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

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

Возвращение структуры с самодокументируемыми именами переменных-членов, вероятно, будет менее подвержено ошибкам для тех, кто использует вашу функцию. Надевая на минутку мою коллегу 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;
}

Таким образом, вы можете четко понимать, что таким образом вы можете возвращать несколько значений из функции. при использовании std::pair могут быть возвращены только 2 значения, в то время как std::tuple может возвращать более двух значений.

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

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

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

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

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

class div{
   public:
      int remainder;

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

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

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

include "boost/tuple/tuple.hpp"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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;
}
Другие вопросы по тегам