Почему кортежи в C ++ не используются чаще?


124

Почему кажется, что никто не использует кортежи в C ++, будь то библиотека Boost Tuple или стандартная библиотека для TR1? Я читал много кода C ++ и очень редко вижу использование кортежей, но я часто вижу много мест, где кортежи решают многие проблемы (обычно возвращая несколько значений из функций).

Кортежи позволяют делать такие классные вещи, как это:

tie(a,b) = make_tuple(b,a); //swap a and b

Это определенно лучше, чем это:

temp=a;
a=b;
b=temp;

Конечно, вы всегда можете сделать это:

swap(a,b);

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

tie(a,b,c) = make_tuple(b,c,a);

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

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


17
+1 за хитрый трюк с подменой кортежей :)
kizzx2

10
a = a ^ b; b = a ^ b; a = a ^ b;
Херардо Марсет,

3
Кортежи IMO удобны в языках со слабой типизацией или языках, в которых они являются родными структурами. Например, в Python или PHP они просто облегчают жизнь, в то время как в C ++ слишком много набора текста (чтобы построить его из шаблона) и слишком мало преимуществ.
doc

4
Комментарий к OP: я думаю, что текущий принятый ответ уже устарел до такой степени, что фактически ошибочен. Возможно, вы захотите пересмотреть выбор принятого ответа.
ulidtko

8
@GerardoMarset Ты серьезно?
thesaint

Ответы:


43

Потому что это еще не стандарт. Все, что нестандартно, имеет гораздо более высокие препятствия. Pieces of Boost стали популярными, потому что программисты требовали их. (на ум приходит hash_map). Но хотя кортеж удобен, это не такая уж очевидная победа, чтобы люди с ним возились.


1
Похоже, люди как сумасшедшие используют другие части Boost. Хотя, конечно, хеш-карты в целом гораздо полезнее, чем кортежи.
Zifre

Я не знаю подробностей того, что вы видите, но предполагаю, что те части, которые люди используют как сумасшедшие, - это функции, которые им действительно, очень нужны. Таким образом (снова угадывая) популярность хеш-карты, счетного указателя и т.п. Кортеж удобен, но это не то, что нужно, чтобы заполнить дыру. Результат не очевиден. Как часто нужно вращать ровно N объектов? (В отличие от необходимости вращать произвольно длинные векторы). И люди привыкли либо передавать возвращаемые значения по ссылке, либо возвращать небольшие классы или структуры.
Алан Де Смет

20
Сейчас это действительно часть стандарта C ++ 11: en.cppreference.com/w/cpp/utility/tuple
Роман Суси

124

Циничный ответ заключается в том, что многие люди программируют на C ++, но не понимают и / или не используют функциональность более высокого уровня. Иногда это потому, что им не разрешают, но многие просто не пытаются (или даже не понимают).

В качестве примера без повышения: сколько людей используют функции, найденные в <algorithm>?

Другими словами, многие программисты на C ++ просто программисты на C, использующие компиляторы C ++ и, возможно, std::vectorи std::list. Это одна из причин, по которой использование boost::tupleне более распространено.


18
-1 от меня, потому что программисты на C ++ не так глупы, как звучит этот ответ.
user541686

5
@Mehrdad Просмотрев много кода C ++, как коммерческого, так и нет, прочитав тонны материалов по C ++, я думаю, можно с уверенностью сказать, что очень большая часть разработчиков "C ++" - это просто разработчики C, которые не могут получить чистый Компилятор C. Шаблоны, например, почти полностью отсутствуют в большинстве материалов (что я очень полюбил). Обычны странные взломы макросов, а пространство имён используется очень мало.
Четче

5
Глупый ответ. Если они действительно понадобятся, они их догонят. Они не нужны; поэтому они не используются. Сказать, что они не используются, потому что их нелегко понять, - плохо.
Майкл Чурдакис

9
@Michael Нонсенс комментарий. Если у вас есть полное по Тьюрингу подмножество языка, в программировании ничего не требуется . Отсутствие использования, конечно же, не означает, что все понимают конструкции C ++ более высокого уровня и предпочитают не использовать их.
Trey Jackson

4
Tbh У меня НИКОГДА не было необходимости в std :: tuple за пределами метапрограммирования вариативного шаблона. Не было никакого смысла в жизни, когда я садился с грустным лицом и думал: «Если бы у меня были эти кортежи». На самом деле, когда я смотрю на кортежи, я думаю: «Какого черта они нужны кому-то здравомыслящему (я бы не считал себя нормальным)». Люди вне метапрограммирования, кажется, используют их как «анонимную структуру», что очень уродливо и указывает на сильное отсутствие качества кода и ремонтопригодности в их головах.
thesaint

23

Синтаксис кортежей C ++ может быть немного более подробным, чем хотелось бы большинству людей.

Рассматривать:

typedef boost::tuple<MyClass1,MyClass2,MyClass3> MyTuple;

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

Все это очень уродливо и взломано по сравнению с чем-то вроде Haskell или Python. Когда здесь появится C ++ 0x и мы получим ключевые слова auto, кортежи станут выглядеть намного привлекательнее.

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


Большинство людей сделают «использование увеличения пространства имен»; и не нужно набирать boost ::. Я не думаю, что ввод кортежа - это большая проблема. Тем не менее, я думаю, вы правы. auto может заставить намного больше людей начать использовать кортежи.
Zifre

2
@Zifre: проблема в том, что вы не должны использовать «пространство имен X» в файле заголовка, потому что это вызывает загрязнение пространства имен и подрывает пространства имен.
Mr Fooz

1
Ах да, я забываю о заголовках. Но внутри программного кода вам не о чем беспокоиться. И как только у нас есть C ++ 0x, мы можем использовать auto, который должен избавить от большого количества типизации.
Zifre

19
Это только я? Я не думаю, что он имел в виду экономию ввода 7 символов "boost ::", а скорее остальных 33 символов . Это чертовски много набирать имена классов, особенно если они тоже ограничены пространством имен. Возьмите boost :: tuple <std :: string, std :: set <std :: string>, std :: vector <My :: Scoped :: LongishTypeName>> как нелепый пример.
Ogre Psalm33

10

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

Лично я не думаю, что кортежи - отличное решение для возврата нескольких значений - звучит как работа для structs.


4
«Я не особо думаю о том, как« лучше »поменять местами» - Когда я пишу код, я пишу ошибки. Снижение сложности кода уменьшает количество ошибок, которые я пишу. Я ненавижу создавать одни и те же ошибки снова и снова. Да, я думаю о том, как лучше <strike> поменять местами </> код . Меньше движущихся частей (LOC, временные переменные, идентификаторы для опечатки), более читаемый код; Хороший код.
sehe

Согласен. Класс, заключенный в автоматический указатель или интеллектуальный указатель, сохраняет тип. Один раз я использовал кортежи, но потом переписал код, используя классы. retValue.state более понятен, чем retValue.get <0> ().
Валентин Хайниц

1
@sehe: Моя цель - написать лучший, более читаемый код. Добавление большего количества типов синтаксиса имеет свои издержки, и я не верю, что «лучший обмен местами» оправдывает умственные затраты на размышления о еще большем количестве типов синтаксиса для каждой строки кода, который вы читаете.
ojrac

8

Но что, если вы хотите повернуть три значения?

swap(a,b);
swap(b,c);  // I knew those permutation theory lectures would come in handy.

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

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

tie(a,b,c) = make_tuple(b*c,a*c,a*b);

немного неудобно распаковывать.

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

tie(a,b,c) = make_tuple(b,c,a);

не делает 6 копий, что делает его совершенно непригодным для некоторых типов (наиболее очевидны коллекции). Не стесняйтесь убедить меня, что кортежи - хорошая идея для "больших" типов, сказав, что это не так :-)

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


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

знаю, почему вы используете кортежи (и я знаю, почему я бы использовал их, если возникнет такая возможность, хотя я не думаю, что когда-либо было). Я догадываюсь, почему другие люди не используют их, даже если знают о них. например, потому что они не согласны с «out params are evil» ...
Стив Джессоп

Знаете ли вы, можно ли заменить «tie (a, b, c) = make_tuple (b, c, a);» на "галстук (a, b, c) = галстук (b, c, a);" ?
Rexxar

2
Связка (технически уровень) - это кортеж, созданный с неконстантными ссылками. Я не могу найти документацию boost, в которой говорится, что гарантирует operator = и конструктор копирования для tie / tuple make, когда некоторые из задействованных ссылок имеют один и тот же референд. Но это то, что вам нужно знать. Наивная реализация оператора = явно может пойти не так ...
Стив Джессоп

1
@Steve: И поскольку все связи связаны с предотвращением копий (они должны работать для некопируемых типов; обратите внимание, что LHS может быть чем-то совершенно другим), действительно, все должно пойти очень не так (подумайте об объектах не-POD). Только представьте, как вы бы написали ту же логику, не используя temps.
sehe

7

Как отмечали многие, кортежи не так полезны, как другие функции.

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

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


6

Не все могут использовать ускорение, а TR1 еще не широко доступен.


3
Многие люди используют Boost. Эти люди также могут использовать кортежи.
Zifre

3
Вы спросили, почему люди их не используют, и я дал один ответ.
Брайан Нил,

2
Голосующему против: я работаю в месте, где политически невозможно использовать boost, и даже на данный момент цепочка инструментов компилятора, которую мы используем (для встроенной системы), не поддерживает TR1 / C ++ 11.
Брайан Нил,

5

При использовании C ++ во встроенных системах получение библиотек Boost становится сложным. Они соединяются друг с другом, поэтому размер библиотеки растет. Вы возвращаете структуры данных или используете передачу параметров вместо кортежей. При возврате кортежей в Python структура данных находится в порядке и типе возвращаемых значений, это просто не явно.


5

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

Скажем, у нас есть функция «f», в которой может иметь смысл возврат кортежа. Как правило, такие функции достаточно сложны и могут выйти из строя.

Если «f» МОЖЕТ выйти из строя, вам понадобится возврат статуса - в конце концов, вы не хотите, чтобы вызывающие абоненты проверяли каждый параметр для обнаружения сбоя. "f", вероятно, вписывается в шаблон:

struct ReturnInts ( int y,z; }
bool f(int x, ReturnInts& vals);

int x = 0;
ReturnInts vals;
if(!f(x, vals)) {
    ..report error..
    ..error handling/return...
}

Это некрасиво, но посмотрите, насколько уродлива альтернатива. Обратите внимание, что мне все еще нужно значение статуса, но код более не читается и не короче. Вероятно, это тоже медленнее, поскольку я беру на себя 1 копию с кортежем.

std::tuple<int, int, bool> f(int x);
int x = 0;
std::tuple<int, int, bool> result = f(x); // or "auto result = f(x)"
if(!result.get<2>()) {
    ... report error, error handling ...
}

Здесь скрывается еще один существенный недостаток - с помощью «ReturnInts» я могу добавить return alter «f», изменив «ReturnInts» БЕЗ ИЗМЕНЕНИЯ ИНТЕРФЕЙСА «f». Решение с кортежем не предлагает этой важной функции, что делает его худшим вариантом для любого библиотечного кода.


1
Исключения делают этот интерфейс намного чище.
Дэвид Стоун

Чтобы быть честным (и очень поздно для вечеринки), вы можете облегчить читаемость, установив using std::tuple;и просто используя tupleв коде.
Alrekr

2
Использование tupleделает код менее читаемым, не более. В настоящее время в большинстве программных кодов содержится очень большое количество символов - видя, std::tupleможно понять, что это такое.
Tom Swirly

3

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

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

Вообще говоря, не у всех уже установлен Boost, и я, конечно, не стал бы загружать его и настраивать свои каталоги include для работы с ним только для его кортежей. Я думаю, вы обнаружите, что люди, уже использующие Boost, с большей вероятностью найдут использование кортежей в своих программах, чем пользователи, не использующие Boost, а мигранты с других языков (на ум приходит Python) с большей вероятностью просто расстроятся из-за отсутствия кортежей в C ++, чем изучать методы добавления поддержки кортежей.


1

Поскольку хранилище данных std::tupleимеет худшие характеристики как structдля массива, так и для массива; весь доступ основан на n-й позиции, но невозможно выполнить итерацию с tupleиспользованием forцикла.

Итак, если элементы в tupleконцептуально являются массивом, я буду использовать массив, а если элементы концептуально не являются массивом, структура (которая имеет именованные элементы) более удобна в обслуживании. ( a.lastnameболее объяснительно, чем std::get<1>(a)).

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


0

У меня такое ощущение, что многие используют Boost.Any и Boost.Variant (с некоторой инженерией) вместо Boost.Tuple.


Зачем менять эффективную статическую типизацию на что-то подобное?
Zifre

Boost.Variant полностью типобезопасен.
user21714

1
Ой, да, это типично, но печатает во время выполнения.
Зифре,

5
Я не понимаю, как можно заменить Tuple Any / Variant. Они не делают то же самое.
Mankarse 03

1
@Zifre Я не могу говорить от имени автора, но я думаю, что здесь подразумевается их использование вместе с другими типами контейнеров.
Тим Сегин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.