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

Iter_swap() и swap() - какая разница?

MSDN говорит:

swap следует использовать вместо iter_swap, который был включен в стандарт С++ для обратной совместимости.

Но comp.std.С++ говорит:

Большинство алгоритмов STL работают на диапазонах итераторов. Поэтому имеет смысл используйте iter_swap при замене элементов в этих диапазонах, поскольку это цель - замена элементов, на которые указывают два итератора. Эта позволяет оптимизировать для node основанных последовательностей, таких как std::list, посредством чего узлы просто перемотаны, а не фактически заменяемые данные.

Итак, какой из них правильный? Должен ли я использовать iter_swap, или я должен использовать swap? (Является iter_swap только для обратной совместимости?) Почему?

4b9b3361

Ответ 1

В самом стандарте очень мало упоминаний iter_swap:

  • Он должен иметь эффект swap(*a, *b), хотя нет никаких указаний на то, что он должен быть реализован таким образом.
  • Различимые значения *a и *b должны быть "заменяемыми", что означает, что swap(*a, *b) должен быть действительным, и, следовательно, типы разыменования должны быть идентичными, хотя типы итераторов не обязательно должны быть.
  • iter_swap требуется использовать при реализации std::reverse. На любой другой алгоритм такого требования нет, поэтому это кажется странным.

Взять то, что нашло из Документы SGI:

Строго говоря, iter_swap избыточно. Он существует только по техническим причинам: в некоторых случаях некоторые компиляторы с трудом выполняют вывод типа, требуемый для интерпретации swap(*a, *b).

Все они, похоже, предполагают, что это артефакт прошлого.

Ответ 2

Это, по-видимому, один из тех сценариев, в которых Интернет создает множество противоречивых сведений.

Стандарт под [C++11: 25.3.3/5] говорит только о том, что iter_swap(a,b) имеет результат swap(*a,*b) (и требует, чтобы "a и b были разысканы" и что "*a должно быть заменено с помощью *b" ), который на первый взгляд коррелирует с интерпретацией MSDN.

Однако я полагаю, что Microsoft пренебрегла рассмотрением правила as-if, которое должно позволить реализации сделать iter_swap быстрее, чем swap в некоторых случаях (например, элементы связанного списка).

Я бы поэтому хотел, чтобы цитата comp.std.c++ была более технически точна из двух.

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

Поэтому я бы предположил, что на практике может быть мало пользы от предпочтения iter_swap над swap, и я бы рекомендовал придерживаться последнего для простоты и согласованности. В любом случае семантика перемещения С++ 11 должна делать swap cinch во многих случаях.

Ответ 3

Да, они оба делают то же самое при правильном использовании. Нет, std::iter_swap не устаревает (помещается в раздел "Особенности совместимости стандартного §D" ). Ссылка MSDN вводит в заблуждение. Проблема в том, что неудобно правильно использовать std::swap.

Вы должны использовать iter_swap по той простой причине, что это более высокая абстракция.

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

using std::swap;
swap( blah, bleh );

не просто

std::swap( blah, bleh );

Это закреплено в § 17.6.3.2, в частности ¶3:

Контекст, в котором оцениваются swap(t, u) и swap(u, t), должен гарантировать, что функция бинарного нечлена с именем "swap" выбирается с помощью разрешения перегрузки (13.3) в наборе кандидатов, который включает в себя:

- два шаблона функции swap, определенные в <utility> (20.2) и

- набор поиска, созданный зависимым от аргумента поиска (3.4.2).

iter_swap не является таким специальным перегруженным именем, и для его настройки требуется добавить специализированную специализацию namespace std {}.


Следовательно, iter_swap полезно инкапсулировать часть интерфейса Swappable, которую вы в противном случае реализовали бы каждый раз.

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


Что касается специализации iter_swap с наблюдаемым другим результатом от swap( *a, *b ), это, казалось бы, несоответствует требованию §25.3.3/5,

Эффекты: swap(*a, *b).

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

Ответ 4

Вы попали в ключевое различие.

swap(*a, *b) - глобальная функция, которая сбрасывает все указатели, указывающие на *a, чтобы указать на то, что было содержимым *b и наоборот. Это старый tmp = a, a = b, b = tmp swap.

iter_swap - для более ограниченного случая изменения базовых объектов, которые повторяются, чтобы повлиять на структуры, частью которых они являются. Если *a и *b были частью одного и того же связанного списка, для iter_swap было достаточно просто поменять свои позиции в списке. Это преимущество, когда вы хотите просто отсортировать список без отмены/изменения внешних указателей на объекты в списке. Если у меня есть указатель на объект user, мне все равно, если вы отсортируете объекты list из user, я не хочу, чтобы моя идея о том, кто является "текущим" пользователем, изменить, поэтому сортировка списка лучше не использовать swap.

Ответ 5

Какую пользу вы должны использовать? Зависит от того, для чего вы его используете. Потому что своп работает только для объектов, заменяя два независимых целых числа или строки или парные. Но iter_swap хорошо работает для массивов и списков, в которых вы можете менять номера в двух разных списках, как показано на cplusplus.com

Ответ 6

Внимательно прочитайте законы:


20.2.2 swap [utility.swap]

  • шаблон void swap (T & a, T & b) noexcept (is_nothrow_move_constructible:: значение && & is_nothrow_move_assignable:: значение);

    2 Требуется: Тип T должен быть MoveConstructible и MoveAssignable. (Таблица 20) и (таблица 22)

    3 Эффекты: Обмен значениями, хранящимися в двух местах.

  • шаблон void swap (T (& a) [N], T (& b) [N]) noexcept (noexcept (swap (* a, * b)));

    4 Требуется: a [i] должно быть заменено с b [i] для всех я в диапазоне [0, N). (17.6.3.2)

    5 Эффекты: swap_ranges (a, a + N, b)

25.3.3 swap [alg.swap]

  • шаблон void iter_swap (ForwardIterator1 a, ForwardIterator2 b);

    5 Эффекты: swap (* a, * b).

    6 Требуется: a и b должны быть разысканы. * a подлежит замене с * b. (17.6.3.2)


Таким образом, iter_swap требуется для обмена значениями, хранящимися в двух разыменованных местах или диапазонах разыменованных мест, и любая попытка обмена ссылками или местоположениями сама по себе ведет борьбу против соответствия. Это явно отключает спекуляцию в отношении оптимизации, лежащей в основе std:: iter_swap. Вместо этого, поскольку Potatoswatter правильно указывал, инкапсуляция и абстракция являются основными причинами ее существования. std:: iter_swap и std:: swap относятся к разным уровням абстракции, так же, как и std:: swap, и любая двоичная функция нечлена с именем "swap" , выбранная с помощью разрешения перегрузки, отличается.

Сменить роли разработчиков и дизайнеров, чтобы понять достижение одного и того же результата, не значит быть одинаковым, так как в "даже объявлении typedef от фундаментального типа это просто шум для компилятора, это не шум для читателя". Возьмем это как шутку, но мы можем утверждать, что весь С++ - это просто неприемлемый артефакт, сокрушающий C, поскольку оба делают то же самое, что и на самом низком уровне, и так далее с любым блоком кода, представляющим абстракцию от другого с помощью обертки. Особенно, когда линия настолько тонкая, как в случае std:: iter_swap, "swap" и std:: swap. Возможно, "использование std:: swap" имеет только несколько символов и исчезает после компиляции, но означает инъекцию идентификатора и построение целого механизма разрешения перегрузки. Внедренные снова и снова, скрепленные снова и снова, заменяются снова и снова, отбрасываются снова и снова. Вдали от абстрактного, инкапсулированного и переработанного подхода.

Воздействие внутренней работы, вызванной верхним слоем, дает и дополнительный потенциал вероятности отказа при обслуживании. В домене свопинга отсутствует (или messing) "использование std:: swap" в глубоком метапрограммирующем дизайне сдерживания будет тихо ждать внутри вашей функции шаблона, ожидая, что трибиально сменный, фундаментальный или c-массив будет разбивать сборку, если повезло или даже StackOverflow (TM) посредством бесконечной рекурсии. Очевидно, что реализация расширяемого механизма должна быть опубликована, но также должна соблюдаться. О тривиально сменяемой памяти, ум ничего moveconstructible и moveassignable может быть заменен на свой собственный тип, даже если ему не хватает перегруженного конверта разрешения подкачки, и действительно есть неясные методы, чтобы отключить нежелательное сменное поведение.

С учетом этого, возможно, все это может быть возобновлено в несовершенство интерпретации самого идентификатора std:: iter_swap: он не поддерживает "обмен итераторами", но "итеративная свопинг". Не обманывайтесь стандартными требованиями по аргументам, являющимся итераторами пересылки: по сути, указатель представляет собой итератор с произвольным доступом, удовлетворяющий таким требованиям. Физически u проходит по указателю, логически u проходит по итератору. Комиссия обычно пытается указать минимальные требования к средству работы с определенным и ожидаемым поведением, не более того. Имя "iterable swapping" правильно раскрывает цель и полномочия механизма. идентификатор "std:: iter_swap", по-видимому, не из-за смятения, но слишком поздно его менять и отменить всю базу кода, на которую опирается.

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

//#include <utility> // std::swap is not required here
#include <algorithm> // std::iter_swap is

namespace linker {

    class base {
    };

    class member {
    };

    template<class M = member, class B = base> // requires swappable base and member
    class link : B {
    public:
        void swap(link &other) { // using iterable swapping
            std::iter_swap(static_cast<B*>(this), static_cast<B*>(&other));
            std::iter_swap(&_member, &other._member);
        }
    private:
        M _member;
    };

    template<class base, class member>
    void swap(link<base,member>& left, link<base,member>& right) { // extending iterable swapping
        left.swap(right);
    }

}

namespace processor {

    template<class A, class B>
    void process(A &a, B &b) { // using iterable swapping
        std::iter_swap(&a, &b);
    }

}

int main() {
#if !defined(PLEASE_NO_WEIRDNESS)
    typedef
        linker::link<
            linker::link<
                linker::link< int[1] >,
                linker::link< void*, linker::link<> >
            >[2],
            linker::link<
                linker::member[3]
            >
        >
    swappable[4]; // just an array of n-ary hierarchies
#else
    typedef linker::link<> swappable;
#endif
    swappable a, b;
    processor::process(a, b);
}

Некоторые интересные моменты в качестве дополнительного руководства:

  • Подкачка означает исключение. Заявление кажется глупым, но это не тот раз, когда вы знаете, что обменная идиома не ориентирована на производительность, а на экстремальную безопасность и надежность.

  • std:: iter_swap демонстрирует одну из многих прекрасных, но упущенных функций метапрограммирования: шаблон не только имеет разрешение перегрузки, но и разрешение пространства имен, что позволяет использовать его в качестве первого в цепочке неизвестных и несвязанных пространств имен. Спасибо, еще одно, о чем беспокоиться.

  • Сменные требования позволяют вам напрямую использовать std:: swap, если (и ТОЛЬКО, ЕСЛИ) u может позволить сделать предположение о том, что обе фундаментальной или c-массиве к фундаментальным типам, что позволяет компилятор для обхода любого разрешения перегрузки. К сожалению, это исключает параметры почти каждого шаблона. Использование std:: swap прямо подразумевает обе цели одного типа (или вынуждены быть одного типа).

  • Не тратьте усилия на объявление возможностей с возможностью замены на тип, который уже тривиально сменяется с самим собой, так же, как наш шаблон ссылки class (попробуйте удалить компоновщик:: swap, поведение не изменится вообще).
    "swap" был разработан как расширяемый для обмена от разных типов,
    автоматический для тех же типов. Обратите внимание, что тип не "заменяемый" или
    "не подлежащий замене" сам по себе, но "с возможностью замены" или "non-swappable-with" другого типа.

Наконец, интересно, сколько читателей заметят

  • 20.2.2 swap [utility.swap]

  • 25.3.3 swap [alg.swap]

и признать, что утилита не является алгоритмом. В реализации Microsoft-Dinkumware, среди прочих, std:: iter_swap просто живет в неправильном заголовке для удобства, что не так. Может быть, это только идентификатор.


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

Лучше рефакторинг, чтобы ваши объекты конечного слоя были недрами, фундаментальными или представляли скрытые более тяжелые объекты (потоковые, если они были достаточно тяжелыми). Объявите, что инициализация ресурсного ресурса должна быть инициализирована (RAII), а как перезагрузки new-delete, так и контейнерные распределители имеют use, чтобы развязать истинные преимущества свопа с нулевым дополнительным усилием. Оптимизируйте ресурсы, чтобы вы перемещали данные только на readquisition, а затем позволяли С++ создавать ваши типы легко, безопасно и быстро по умолчанию.

Девиз: В прежние времена люди боролись с данными, которые были слишком толстыми в памяти, слишком медленными на диске. В настоящее время векторы-итераторы фильтруются из пулов хранилищ и потоки обрабатываются в параллельных трубах. Завтра машины будут ездить в одиночку. Заслуживает PostIt.