Boost :: Tuples vs Struct для возвращаемых значений - PullRequest
43 голосов
/ 03 января 2009

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

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

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

struct divide_result {
    int quotient;
    int remainder;
};

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

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

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

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

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

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

Ответы [ 9 ]

24 голосов
/ 04 января 2009

кортежи

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

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

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

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

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

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

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

возвращая структуру

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

div_result d = div(10, 3);

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

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

cout << div(10, 3);

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

10 голосов
/ 04 января 2009

Другой вариант - использовать карту 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);

Также есть и другие преимущества, такие как возможность повторять над полями карты и т. Д. И т. Д. См. doco для получения дополнительной информации.

5 голосов
/ 04 января 2009

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

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

4 голосов
/ 04 января 2009

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

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

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

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

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

3 голосов
/ 25 июня 2009

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

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
}

Компилятор не может инициализировать "Tuple" с результатом make_tuple, и поэтому генерирует ошибку, которая позволяет вам указать правильные значения для третьего параметра.

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

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

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}
3 голосов
/ 04 января 2009

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

//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);
2 голосов
/ 04 января 2009

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

2 голосов
/ 04 января 2009

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

0 голосов
/ 04 января 2009

Я согласен с вами на 100%, Родди.

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

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

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

Кортежи злые.

...