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

Boost:: Tuples vs Structs для возвращаемых значений

Я пытаюсь завязать кортежи (спасибо @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
};

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

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

4b9b3361

Ответ 1

кортежи

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

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>>. Если вы не связаны с операторами, а контекст не кристально чист, я рекомендую вам использовать указатели, чтобы сигнализировать на стороне вызова, что объект фактически используется как выходной параметр, в дополнение к комментариям, где это необходимо.

возвращает struct

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

div_result d = div(10, 3);

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

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

cout << div(10, 3);

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

Ответ 5

Я обычно использую кортежи в сочетании с 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);

Ответ 6

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

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, и поэтому генерирует ошибку, которая позволяет вам указать правильные значения для третьего параметра.

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

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

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

Кортежи злы.