iter_swap() и swap() в чем разница?

MSDN сообщает:

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

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

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

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


person user541686    schedule 24.12.2012    source источник
comment
stackoverflow.com/questions/13991909/ тем не менее довольно полно охватывает тему.   -  person Lightness Races in Orbit    schedule 24.01.2013
comment
Во второй цитате ошибка. iter_swap никогда не может повторно связать узлы связанного списка, потому что тогда пользователь, удерживающий итератор в связанном списке, увидит, что содержимое заменено на swap(*a, *b), но не на iter_swap(a, b), что противоречит требованию, что iter_swap(a, b) должен вести себя как swap(*a, *b). Ну а желательно ли это - совсем другой вопрос...   -  person Marc Mutz - mmutz    schedule 20.07.2015
comment
@MarcMutz-mmut: Да, я понял это через некоторое время после публикации вопроса. (Я сделал тот же комментарий под одним из ответов.)   -  person user541686    schedule 20.07.2015
comment
Я обнаружил, что если базовый итератор возвращает значение (временное r-значение) вместо ссылки, то лучше реализовать std::iter_swap для этого специального типа итератора, а не перегружать искусственный std::swap для значений или аргументов r-значения. .   -  person alfC    schedule 06.11.2016
comment
Соответствующая статья, в которой упоминается возможная полезность iter_swap ericniebler.com/2015/02 /03/итераторы-плюс-плюс-часть-1   -  person Predelnik    schedule 13.03.2018
comment
Я бы ожидал обе операции от iter_swap: std::swap(*it1, *it2); и std::swap(it1, it2); так что переменные итератора следуют за замененными значениями без необходимости изменять контейнер (оплачивая своп вместо двойной связи) => Я чувствую себя потерянным с этой функцией как наивный пользователь ^^   -  person Cevik    schedule 09.01.2021


Ответы (6)


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

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

Чтобы позаимствовать то, что sehe нашел у документы SGI:

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

Все это, кажется, предполагает, что это артефакт прошлого.

person Rufflewind    schedule 24.01.2013
comment
Вы правы, я забыл, что целью iter_swap является обмен элементами, на которые указывают итераторы. Я обновил свой ответ, чтобы он был менее запутанным. - person Rufflewind; 24.01.2013
comment
И еще я вижу, что iter_swap используется в моем современном libstdc++ (для gcc версии 10), например, в c++/10/bits/stl_algo.h он используется 26 раз, в том числе в некоторых разделах C++17. - person Elliott; 09.01.2021

Кажется, это один из тех сценариев, в которых Интернет производит множество противоречивой информации.

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

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

Поэтому я полагаю, что цитата comp.std.c++ является более точной с технической точки зрения из двух.

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

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

person Lightness Races in Orbit    schedule 24.01.2013
comment
Итак, я только что понял, что iter_swap и swap не дают одинаковых эффектов, если iter_swap повторно связывает узлы. Если iter_swap повторно связывает узлы, то указатели на старый объект по-прежнему будут указывать на тот же объект, тогда как в случае swap они будут ссылаться на новый объект. Тогда это правильная реализация? Или единственная реалистичная реализация просто вызывает swap? - person user541686; 21.05.2013
comment
Должен ли я задать новый вопрос? Думал, что это будет дубликат этого :\ - person user541686; 21.05.2013
comment
@Mehrdad: Возможно, предложите награду. SO здесь немного разваливается. В любом случае, я проведу небольшое исследование, когда у меня будет время. - person Lightness Races in Orbit; 21.05.2013
comment
Я просто положил награду! Будем надеяться, что это сработает, и мы не получим глупых ответов. - person user541686; 21.05.2013
comment
лол, так ты только что резюмировал мой комментарий в своем ответе? Я не вижу там ничего нового, что могло бы поддержать или противостоять этому... - person user541686; 25.05.2013
comment
@Mehrdad: я подумал о ситуации и пришел к тому же выводу, что и вы, и Potatoswatter. Там нет ничего нового, потому что ничего нового не требуется: тема регулируется правилами и формулировками, которые я уже освещал в исходной версии моего ответа. - person Lightness Races in Orbit; 25.05.2013

Да, они оба делают одно и то же, при правильном использовании. Нет, 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 действительны как до, так и после операции. К сожалению, это ошибка в реализации библиотеки.

person Potatoswatter    schedule 24.01.2013
comment
Примечание для будущих читателей (этот ответ был здесь до награды) - я пытаюсь понять цель iter_swap, поэтому этот ответ не совсем помогает, поскольку он не говорит мне, что это такое. - person user541686; 23.05.2013
comment
К сожалению, нет, это не имеет смысла. При правильном использовании? это просто напрашивается вопрос о том, что является правильным использованием и что является неправильным использованием, и почему они оба существуют, когда они делают одно и то же. (Очевидно, они не добавили iter_swap для поддержки неправильных вариантов использования, не так ли?) - person user541686; 24.05.2013
comment
@Mehrdad Вот что изначально объяснял ответ. Правильное использование простого swap требует неудобного объявления using. Только сейчас я увидел вашу заботу в ящике с наградами и добавил раздел в конец. - person Potatoswatter; 24.05.2013
comment
Итак, вы говорите, что причина, по которой они включили это, заключается в том, чтобы не говорить using std::swap; когда цели итераторов меняются местами? Мне чрезвычайно трудно это купить. Они редко включают что-то для таких узких вариантов использования. Должен быть лучший ответ (также лучше, чем более высокий уровень абстракции; это также не является достаточной причиной для включения чего-либо в стандартную библиотеку). - person user541686; 24.05.2013
comment
@Mehrdad Этого недостаточно? Замена целей итераторов - это весь вариант использования, что еще может быть потенциально? using std::swap не является узким подразделом; объявление using требуется для любого вызова swap в соответствии с интерфейсом Swappable. - person Potatoswatter; 24.05.2013
comment
Нет, это не так. Помочь пользователю избежать ввода 16 лишних символов using std::swap; не является причиной для включения чего-либо в стандартную библиотеку. Это работа написанных пользователем вспомогательных функций, таких как boost::swap. - person user541686; 24.05.2013
comment
@Mehrdad Возможно, они не добавили бы его сейчас, но он был перенесен из SGI STL, когда некоторые компиляторы не могли обработать выражение swap(*a,*b). Но это все еще более чем достаточно полезно, чтобы избежать устаревания. - person Potatoswatter; 24.05.2013
comment
Это совершенно другая причина, ничего не имеющая отношения к проблеме using. Если это настоящая причина, то я думаю, что у fzlogic был правильный ответ с самого начала, может быть, я просто дам ему награду. - person user541686; 25.05.2013
comment
Что ограничивает эволюцию только одной причиной? Он был добавлен для одной цели, а теперь служит другой аналогичной цели. Слабым звеном стали не компиляторы, а программисты. std::swap — одна из немногих, если не единственная функция, которую ADL должен вызывать. Вы слегка меняете вопрос: следует ли использовать iter_swap или использовать swap? (iter_swap только для обратной совместимости?) — история здесь не имеет значения. MSDN абсолютно неправильный и поощряет неправильное использование, как они обычно делают. - person Potatoswatter; 25.05.2013
comment
Это должен быть принятый ответ. индивидуальную подкачку трудно сделать правильно. Использование iter_swap избавляет от многих хлопот. - person John Z. Li; 03.05.2019

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

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

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

person Old Pro    schedule 25.05.2013

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

person Sír Jeff Meadows    schedule 19.01.2013

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

20.2.2 своп [utility.swap]

  • template void swap(T& a, T& b) noexcept(is_nothrow_move_constructible::value &&
    is_nothrow_move_assignable::value); 2 Требуется: тип T должен быть MoveConstructible и MoveAssignable. (Таблица 20) и (Таблица 22) 3 Эффекты: Обмен значениями, хранящимися в двух местах.
  • template void swap(T (&a)[N], T (&b)[N]) noexcept(noexcept(swap(*a, *b))); 4 Требуется: a[i] должен быть заменен на b[i] для всех i в диапазоне [0,N). (17.6.3.2) 5 Эффекты: swap_ranges(a, a + N, b)

25.3.3 подкачка [alg.swap]

  • шаблон недействителен iter_swap (ForwardIterator1 a, ForwardIterator2 b); 5 эффектов: поменять местами (*a, *b). 6 Требует: a и b должны быть разыменуемыми. *a можно заменить на *b. (17.6.3.2)

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

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

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

Имея это в виду, возможно, все это может быть продолжено в неправильной интерпретации самого идентификатора std::iter_swap: он означает не «подкачка итератора», а «итерируемая подкачка». Не дайте себя обмануть стандартными требованиями к аргументам, являющимся прямыми итераторами: по сути, указатель — это итератор с произвольным доступом, таким образом удовлетворяющий требованиям. Физически вы проходите по указателю, логически вы проходите через итератор. Комиссия обычно пытается указать минимальные требования для работы объекта с определенным и ожидаемым поведением, не более того. Название «итерируемая подкачка» правильно раскрывает цель и возможности механизма. идентификатор "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 напрямую, если (и ТОЛЬКО ЕСЛИ) вы можете позволить предположить, что обе цели относятся к фундаментальным типам или c-array к фундаментальным типам, что позволяет компилятору обходить любое разрешение перегрузки. К сожалению, это исключает параметры почти каждого шаблона. Использование std::swap напрямую подразумевает, что обе цели относятся к одному типу (или принудительно относятся к одному типу).

  • Не тратьте усилия на объявление заменяемых возможностей для типа, который уже тривиально заменяется сам с собой, как и наш класс шаблона ссылки (попробуйте удалить linker::swap, поведение совсем не изменится).
    «swap» был разработаны с возможностью расширения для переключения между различными типами,
    автоматически для одного и того же типа. Имейте в виду, что тип не является "заменяемым" или
    "не заменяемым" сам по себе, а "заменяемым-с" или
    "незаменяемым-с" другим типом.

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

  • 20.2.2 своп [utility.swap]

  • 25.3.3 подкачка [alg.swap]

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


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

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

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

person tadevt    schedule 26.05.2013
comment
В качестве примечания: если бы вы выдвигали документы SGI, вы бы выдвигали: ForwardIterator1 и ForwardIterator2 имеют одинаковый тип значения. Wrong, ForwardIterator1 и ForwardIterator2 ссылаются на объекты с заменяемым типом значения. В противном случае любое использование std::iter_swap для разных типов будет незаконным. Девиз: не толкайте ничего, кроме закона, переводчики и исполнители, как известно, часто занимаются фристайлом. - person tadevt; 27.05.2013
comment
Итак, этот ответ говорит, что iter_swap и swap принадлежат к разным уровням абстракции. Это не поможет без дополнительных деталей: я понятия не имею, какие там слои. Вы должны хотя бы указать, какой слой выше (если они сопоставимы). Кроме того, что показывает фрагмент кода? Кроме того, фразы типа «не на моих часах» неконструктивны (были бы лучше пояснения почему бы и нет). - person anatolyg; 04.01.2015