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
};
... который не наполнил бы меня уверенностью.
Итак, каковы преимущества кортежей над структурами, которые компенсируют двусмысленность?
Ответы
Ответ 1
кортежи
Думаю, я согласен с вами в том, что проблема с какой позицией соответствует той переменной, которая может ввести путаницу. Но я думаю, что есть две стороны. Один из них - это call-side, а другой - сторона вызова:
int remainder;
int quotient;
tie(quotient, remainder) = div(10, 3);
Я думаю, что это ясно, что мы получили, но это может стать запутанным, если вам нужно вернуть больше значений сразу. Как только программист вызывающего абонента просмотрел документацию div
, он будет знать, какая именно позиция, и может написать эффективный код. Как правило, я бы сказал, чтобы не возвращать сразу более 4 значений. Для чего угодно, предпочитайте структуру.
выходные параметры
Конечно, можно использовать выходные параметры:
int remainder;
int quotient;
div(10, 3, "ient, &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 вы подчеркиваете эту точку, плюс каждый элемент получает имя, которое оно заслуживает.
Кортежи злы.