Boost::Tuples против структур для возвращаемых значений

StackOverflow https://stackoverflow.com/questions/409827

  •  03-07-2019
  •  | 
  •  

Вопрос

Я пытаюсь разобраться в кортежах (спасибо @litb), и обычно их используют для функций, возвращающих значение > 1.

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

Заимствование примера, я бы использовал это

struct divide_result {
    int quotient;
    int remainder;
};

Используя кортеж, вы бы получили

typedef boost::tuple<int, int> divide_result;

Но не читая код функции, которую вы вызываете (или комментарии, если вы настолько глупы, чтобы им доверять), вы понятия не имеете, какое целое число является частным, и наоборот.Это скорее похоже на...

struct divide_result {
    int results[2]; // 0 is quotient, 1 is remainder, I think
};

...что не придало бы мне уверенности.

Ну и что являются преимущества кортежей перед структурами, компенсирующими двусмысленность?

Это было полезно?

Решение

кортежи

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

int remainder; 
int quotient;
tie(quotient, remainder) = div(10, 3);

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

выходные параметры

Выходные параметры, конечно, тоже можно использовать:

int remainder; 
int quotient;
div(10, 3, &quotient, &remainder);

Теперь я думаю, что это иллюстрирует, насколько кортежи лучше выходных параметров.Мы смешали входные данные div с его выходом, не получив при этом никакого преимущества.Хуже того, мы оставляем читателя этого кода в сомнении относительно того, что может быть действительный возвращаемое значение div быть.Там являются замечательные примеры, когда выходные параметры полезны.По моему мнению, вам следует использовать их только тогда, когда у вас нет другого выхода, поскольку возвращаемое значение уже занято и не может быть изменено ни на кортеж, ни на структуру. operator>> является хорошим примером использования выходных параметров, поскольку возвращаемое значение уже зарезервировано для потока, поэтому вы можете связать operator>> звонки.Если вы не имеете дело с операторами и контекст не совсем ясен, я рекомендую вам использовать указатели, чтобы сигнализировать на стороне вызова о том, что объект действительно используется в качестве выходного параметра, в дополнение к комментариям, где это необходимо.

возврат структуры

Третий вариант — использовать структуру:

div_result d = div(10, 3);

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

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

cout << div(10, 3);

И отобразите свой результат.Я думаю, что кортежи, с другой стороны, явно выигрывают за свои универсальный природа.Делая это с помощью div_result, вам нужно перегрузить оператор<< или выводить каждый элемент отдельно.

Другие советы

Другой вариант — использовать карту Boost Fusion (код не тестировался):

struct quotient;
struct remainder;

using boost::fusion::map;
using boost::fusion::pair;

typedef map<
    pair< quotient, int >,
    pair< remainder, int >
> div_result;

Вы можете получить доступ к результатам относительно интуитивно:

using boost::fusion::at_key;

res = div(x, y);
int q = at_key<quotient>(res);
int r = at_key<remainder>(res);

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

С кортежами вы можете использовать tie, что иногда бывает весьма полезно: std::tr1::tie (quotient, remainder) = do_division ();.Со структурами это не так просто.Во-вторых, при использовании кода шаблона иногда проще полагаться на пары, чем добавлять еще одно определение типа для типа структуры.

А если типы разные, то пара/кортеж действительно не хуже структуры.Подумайте, например pair<int, bool> readFromFile(), где int — это количество прочитанных байт, а bool — был ли достигнут eof.Добавление структуры в этом случае кажется мне излишним, тем более, что здесь нет никакой двусмысленности.

Кортежи очень полезны в таких языках, как ML или Haskell.

В C++ их синтаксис делает их менее элегантными, но может быть полезен в следующих ситуациях:

  • у вас есть функция, которая должна возвращать более одного аргумента, но результат является «локальным» для вызывающего и вызываемого объекта;вы не хотите определять структуру только для этого

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

  • они поставляются с предопределенными операторами <, которые могут сэкономить время.

Я склонен использовать кортежи в сочетании с определениями типов, чтобы хотя бы частично облегчить проблему «безымянного кортежа».Например, если бы у меня была сетка, то:

//row is element 0 column is element 1
typedef boost::tuple<int,int> grid_index;

Затем я использую именованный тип как:

grid_index find(const grid& g, int value);

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

Или в вашем примере:

//quotient is element 0 remainder is element 1
typedef boost:tuple<int,int> div_result;
div_result div(int dividend,int divisor);

Одна особенность кортежей, которой нет в структурах, заключается в их инициализации.Рассмотрим что-то вроде следующего:

struct A
{
  int a;
  int b;
};

Если только ты не напишешь make_tuple эквивалент или конструктор, то для использования этой структуры в качестве входного параметра сначала необходимо создать временный объект:

void foo (A const & a)
{
  // ...
}

void bar ()
{
   A dummy = { 1, 2 };
   foo (dummy);
}

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

struct A
{
  int a;
  int b;
  int c;
};

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

Сравните это с кортежем:

typedef boost::tuple<int, int, int> Tuple;
enum {
  A
  , B
  , C
};

void foo (Tuple const & p) {
}

void bar ()
{
  foo (boost::make_tuple (1, 2));  // Compile error
}

Компилятор не может инициализировать «Кортеж» результатом make_tuple, и таким образом генерирует ошибку, позволяющую указать правильные значения для третьего параметра.

Наконец, еще одно преимущество кортежей заключается в том, что они позволяют писать код, который перебирает каждое значение.Это просто невозможно с использованием структуры.

void incrementValues (boost::tuples::null_type) {}

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}

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

Кортежи писать будет проще — не нужно создавать новую структуру для каждой функции, которая что-то возвращает.Документация о том, что и куда пойдет в документацию по функциям, которая в любом случае понадобится.Чтобы использовать функцию, в любом случае необходимо прочитать документацию по функции, и там будет описан кортеж.

Я согласен с тобой на 100%, Родди.

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

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

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

Кортежи — это зло.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top