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

Если вы перегружаете swap в пространстве имен std?

Я прочитал что-то интересное сегодня, в котором говорится, что "стандартный" способ вызова swap для предоставленного пользователем типа (предоставленный как аргумент шаблона)...

using std::swap;
swap(something, soemthingelse);

Причиной этого является использование зависимого от аргументов поиска для использования функции swap в пространстве имен пользователей или swap в пространстве имен std. Это вызвало у меня интересный вопрос. Когда я перегружал std::swap для одного из моих классов, я фактически определял его в пространстве имен std... namespace std { void swap(/*...*/){/*...*/} }. Является ли эта практика неправильной? Должен ли я определить свой собственный swap в std или мое собственное пространство имен (и почему)?

4b9b3361

Ответ 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 будет неэффективен для вашего типа. В случае, если вы предоставляете функцию члена подкачки, рекомендуется также предоставить не-членский обмен, который вызывает эту функцию-член. Это облегчает для других людей более эффективную версию шаблона.