Boost::Tuples vs Struct для возвращаемых значений
Я пытаюсь разобраться с кортежами (спасибо @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 ответов
кортежи
Я думаю, что я согласен с вами, что вопрос, с какой позицией соответствует какая переменная, может ввести в заблуждение. Но я думаю, что есть две стороны. Одна сторона вызова, а другая сторона вызова:
int remainder;
int quotient;
tie(quotient, remainder) = div(10, 3);
Я думаю, что кристально ясно, что мы получили, но это может запутать, если вам придется возвращать больше значений одновременно. Однажды программист вызывающего абонента посмотрел документацию div
Он будет знать, какая позиция, и может написать эффективный код. Как правило, я бы сказал, чтобы не возвращать более 4 значений одновременно. Для чего-то еще, предпочитайте структуру.
выходные параметры
Конечно, можно использовать и выходные параметры:
int remainder;
int quotient;
div(10, 3, "ient, &remainder);
Теперь я думаю, что это показывает, насколько кортежи лучше, чем выходные параметры. Мы смешали ввод div
с его выходом, при этом не получая никакого преимущества. Хуже того, мы оставляем читателя этого кода под сомнением относительно того, что может быть фактическим возвращаемым значением div
быть. Есть замечательные примеры, когда выходные параметры полезны. На мой взгляд, вы должны использовать их только тогда, когда у вас нет другого пути, потому что возвращаемое значение уже взято и не может быть изменено на кортеж или структуру. operator>>
хороший пример того, где вы используете выходные параметры, потому что возвращаемое значение уже зарезервировано для потока, так что вы можете связать operator>>
звонки. Если вы не имеете дело с операторами, и контекст не кристально ясен, я рекомендую вам использовать указатели, чтобы сигнализировать на стороне вызова, что объект фактически используется в качестве выходного параметра, в дополнение к комментариям, где это уместно.
возвращая структуру
Третий вариант - использовать структуру:
div_result d = div(10, 3);
Я думаю, что это определенно выигрывает награду за ясность. Но учтите, что вам все еще нужно получить доступ к результату в этой структуре, и результат не "обнажается" в таблице, как это было в случае выходных параметров и кортежа, используемого с tie
,
Я думаю, что основной момент в наши дни - сделать все как можно более общим. Итак, скажем, у вас есть функция, которая может распечатывать кортежи. Вы можете просто сделать
cout << div(10, 3);
И ваш результат отображается. Я думаю, что кортежи, с другой стороны, явно выигрывают за их универсальность. Делая это с помощью div_result, вам нужно перегрузить operator<< или вывести каждый элемент отдельно.
Другой вариант - использовать карту 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 ();
, Это не так просто с структурами. Во-вторых, при использовании шаблонного кода иногда проще полагаться на пары, чем добавлять еще один typedef для типа структуры.
А если типы разные, то пара / кортеж на самом деле не хуже структуры. Подумай например pair<int, bool> readFromFile()
где int - это количество прочитанных байтов, а bool - было ли выполнено действие eof. Добавление структуры в этом случае кажется мне излишним, тем более что здесь нет двусмысленности.
Кортежи очень полезны в таких языках, как ML или Haskell.
В C++ их синтаксис делает их менее элегантными, но может быть полезен в следующих ситуациях:
у вас есть функция, которая должна возвращать более одного аргумента, но результат является "локальным" для вызывающей стороны и вызываемой стороны; Вы не хотите определять структуру только для этого
Вы можете использовать функцию связи, чтобы сделать очень ограниченную форму сопоставления с образцом "а-ля ML", что более элегантно, чем использование структуры для той же цели.
они поставляются с предопределенными операторами <, которые могут сэкономить время.
Я склонен использовать кортежи в сочетании с 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);
Одной из особенностей кортежей, которых у вас нет со структурами, является их инициализация. Рассмотрим что-то вроде следующего:
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 ());
}
Кортежи будут легче писать - не нужно создавать новую структуру для каждой функции, которая что-то возвращает. Документация о том, что идет, куда пойдет, к документации по функциям, которая в любом случае понадобится. Чтобы использовать функцию, нужно в любом случае прочитать документацию по функции, и там будет объяснен кортеж.
Предотвращает засорение вашего кода множеством структурных определений. Человеку, пишущему код, легче, и другим, использующим его, когда вы просто документируете, что представляет собой каждый элемент в кортеже, вместо того, чтобы писать собственную структуру / заставлять людей искать определение структуры.
Я согласен с тобой на 100%, Родди.
Чтобы вернуть несколько значений из метода, у вас есть несколько параметров, кроме кортежей, какой из них лучше всего зависит от вашего случая:
Создание новой структуры. Это хорошо, когда возвращаемые множественные значения связаны, и целесообразно создать новую абстракцию. Например, я думаю, что "div_result"- хорошая общая абстракция, и передача этой сущности делает ваш код намного понятнее, чем просто передача безымянного кортежа. Затем вы можете создать методы, которые работают с этим новым типом, преобразовать его в другие числовые типы и т. Д.
Использование параметров "Out". Передайте несколько параметров по ссылке и верните несколько значений, присваивая каждому параметру out. Это уместно, когда ваш метод возвращает несколько несвязанных фрагментов информации. Создание новой структуры в этом случае было бы излишним, и с параметрами Out вы подчеркиваете этот момент, плюс каждый элемент получает имя, которого он заслуживает.
Кортежи злые.