Если вы перегружаете swap в пространстве имен std?
Я прочитал что-то интересное сегодня, в котором говорится, что "стандартный" способ вызова swap для предоставленного пользователем типа (предоставленный как аргумент шаблона)...
using std::swap;
swap(something, soemthingelse);
Причиной этого является использование зависимого от аргументов поиска для использования функции swap
в пространстве имен пользователей или swap
в пространстве имен std
. Это вызвало у меня интересный вопрос. Когда я перегружал std::swap
для одного из моих классов, я фактически определял его в пространстве имен std
... namespace std { void swap(/*...*/){/*...*/} }
. Является ли эта практика неправильной? Должен ли я определить свой собственный swap
в std
или мое собственное пространство имен (и почему)?
Ответы
Ответ 1
Вы делаете это неправильно:)
17.6.2.4.1 [namespace.std]
-
- Поведение программы на С++ undefined, если оно добавляет объявления или определения в пространство имен
std
или в пространство имен в пространстве имен std
, если не указано иное. Программа может добавлять специализацию шаблона для любого стандартного шаблона библиотеки в пространство имен std
только в том случае, если объявление зависит от пользовательского типа, а специализация соответствует требованиям стандартной библиотеки для исходного шаблона и явно не запрещена.
Это довольно ясно говорит, что вы не можете добавлять перегрузки в пространство имен std
. Вы можете специализировать std::swap<MyType>
для своего типа, но если ваш тип является шаблоном, вам потребуется частичная специализация, std::swap<MyContainer<T>>
, и вы не можете частично специализировать шаблон функции, чтобы это не сработало, так что это не хороший подход в целом.
С++ 11 также определяет требования к подлежащему замене типам, которые включают:
17.6.3.2 [swappable.requirements]
-
- ...
- ...
- Контекст, в котором оцениваются
swap(t, u)
и swap(u, t)
, должен гарантировать, что функция бинарного нечлена с именем "swap" выбирается с помощью разрешения перегрузки (13.3) в наборе кандидатов, который включает в себя:
-
- два шаблона функции свопинга, определенные в
<utility>
(20.2) и - набор поиска, созданный зависимым от аргумента поиска (3.4.2).
Таким образом, вызов swap
на двух объектах типа swappable должен быть в состоянии найти std::swap
и должен иметь возможность находить другие перегрузки ADL. Вызов безоговорочной (и без явного списка аргументов шаблона) обеспечивает ADL и включает <utility>
и добавление декларации использования для std::swap
гарантирует, что стандартные перегрузки могут быть найдены. Так что сделайте так, как вы показываете в своем вопросе, отвечаете этим требованиям.
Это довольно четко определяет, что требуется для замены в смысле, используемом стандартом, что требуется стандартной библиотеке, например. функциями из <algorithm>
.
Если вы помещаете swap
перегрузки для вашего типа в пространство имен типов, то они могут быть найдены ADL. Так или иначе, функции, связанные с вашим типом, принадлежат к тому же пространству имен, что и ваш тип, см. Пункт 57 в С++ Standards Standards Саттер и Александреску для более подробной информации по этой теме.
Короче говоря, вы делаете это неправильно. То, что вы читаете, является правильным. Выполнение using std::swap
и использование ADL всегда работают (для шаблонов и не-шаблонов) и избегают поведения undefined. Yay.
N.B. В стандарте С++ 03 было менее ясно, как должны меняться пользовательские типы. Для некоторой истории вокруг этой области см. N1691 2.2, которая определяет термин "точка настройки" и показывает различные способы определения их в API. Протокол, используемый в С++ 11 для типов подкачки, использует один из этих способов, и теперь он четко и недвусмысленно благословляется как "правильный способ" для обеспечения функции свопинга для вашего типа. Другие точки настройки в других библиотеках могут использовать другие подходы, но для замены в С++ 11 термины означают using std::swap;
и полагаются на ADL.
Ответ 2
Законно предоставлять специализации стандартных шаблонов для ваших собственных типов, и они должны войти в пространство имен std
. Таким образом, оба подхода являются законными С++.
При этом рекомендуется использовать swap
бесплатную функцию в том же пространстве имен, что и ваш собственный тип, и теперь ADL может перехватить вашу перегрузку.
EDIT. После некоторых комментариев я перечитал вопрос и заметил, что он упоминает перегрузки в пространстве имен std
. Это незаконно, чтобы обеспечить перегрузки в пространстве имен std
, разрешены только специализации.
Ответ 3
Я верю этот ответ, который вы ищете, и весь набор ответов на этот вопрос объясняет все. Ховард Хиннант и Дейв Абрахамс уже более десяти лет находятся в комитете по стандартам C++.
Ответ 4
Прежде всего, вам не разрешено добавлять вещи в std
(это просто перегрузка), поэтому перегрузка std::swap
- это, во-первых, плохая практика. Что вы можете сделать, это специализировать существующие шаблоны. Поэтому вы должны скорее специализироваться, чем перегружать:
namespace std
{
template<> void swap<MyType>(...) {...}
}
Но все же вопрос стоит, если версия собственного пространства имен предпочтительнее, чем std
-специализация. Рекомендуемым способом является предоставление бесплатного swap
в вашем собственном пространстве имен и разрешение ADL, как это уже предлагает Дэвид. Проблема заключается в том, что если кто-то (клиент вашей библиотеки) не настолько хорошо разбирается в идиоматическом С++ и просто явно называет std::swap(...)
повсеместно, вы можете столкнуться с субоптимальным изменением поведения. Это тот случай, почему я для себя обычно пошел безопасным способом делать и то, и другое (функция пространства имен + std
-специализация), даже если это всегда имеет плохой вкус для меня.
Но, к счастью, С++ 11 упрощает вещи, поскольку эффективно перемещаемые типы (которые обычно также эффективно заменяемые) могут быть довольно эффективно меняться по умолчанию std::swap
, который использует семантику move. Таким образом, возможное использование по умолчанию std::swap
по вашей собственной функции swap
становится менее неблагоприятным (и вы даже можете подумать о том, чтобы не допустить его в любом случае).
Итак, если у вас есть С++ 11, лучший способ - не пропустить расширение std
, а просто определить собственное swap
в пространстве имен ваших типов, тогда как в С++ 03 вы можете захотеть безопасная сторона с специализацией std::swap
, даже если она менее идиоматична.
Ответ 5
Идеальная вещь - предоставить функцию public public swap только тогда, когда вы считаете, что std:: swap будет неэффективен для вашего типа.
В случае, если вы предоставляете функцию члена подкачки, рекомендуется также предоставить не-членский обмен, который вызывает эту функцию-член. Это облегчает для других людей более эффективную версию шаблона.