std::swap()
используется многими контейнерами std (такими как std::list
и std::vector
) во время сортировки и даже присвоения.
Но std-реализация swap()
очень обобщена и довольно неэффективна для пользовательских типов.
Таким образом, эффективность может быть достигнута путем перегрузки std::swap()
с реализацией, специфичной для пользовательских типов. Но как реализовать ее так, чтобы она использовалась контейнерами std?
Правильный способ перегрузить swap - записать его в том же пространстве имен, что и то, что вы меняете, чтобы его можно было найти через argument-dependent lookup (ADL). Особенно легко это сделать следующим образом:
class X
{
// ...
friend void swap(X& a, X& b)
{
using std::swap; // bring in swap for built-in types
swap(a.base1, b.base1);
swap(a.base2, b.base2);
// ...
swap(a.member1, b.member1);
swap(a.member2, b.member2);
// ...
}
};
Внимание Mozza314
Вот моделирование эффектов универсального std :: algorithm
, вызывающего std :: swap
, и когда пользователь предоставляет свой своп в пространстве имен std. Поскольку это эксперимент, это моделирование использует namespace exp
вместо namespace std
.
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
exp::swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
namespace exp
{
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
Для меня это распечатывается:
generic exp::swap
Если ваш компилятор печатает что-то другое, то он неправильно реализует «двухфазный поиск» для шаблонов.
Если ваш компилятор соответствует (любому из C ++ 98/03/11), то он даст тот же вывод, который я показываю. И в этом случае именно то, чего вы боитесь, произойдет. И помещение вашего swap
в пространство имен std
(exp
) не помешало этому случиться.
Дейв и я оба являемся членами комитета и работаем в этой области стандарта в течение десятилетия (и не всегда согласны друг с другом). Но этот вопрос был решен в течение длительного времени, и мы оба согласны с тем, как он был решен. Не обращайте внимания на экспертное мнение / ответ Дейва в этой области на свой страх и риск.
Этот выпуск стал известен после публикации C ++ 98. Начиная примерно с 2001 года мы с Дейвом начали работать в этой области. И это современное решение
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
void swap(A&, A&)
{
printf("swap(A, A)\n");
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
Выход это:
swap(A, A)
Обновить
Было сделано наблюдение, что:
namespace exp
{
template <>
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
работает! Так почему бы не использовать это?
Рассмотрим тот случай, когда ваш A
является шаблоном класса:
// simulate user code which includes <algorithm>
template <class T>
struct A
{
};
namespace exp
{
template <class T>
void swap(A<T>&, A<T>&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A<int> a[2];
exp::algorithm(a, a+2);
}
Теперь это не работает снова. :-(
Таким образом, вы можете поместить swap
в пространство имен std и заставить его работать. Но вам нужно помнить, чтобы поместить swap
в пространство имен A
для случая, когда у вас есть шаблон: A < T >
. И поскольку оба случая будут работать, если вы поместите «swap» в пространство имен «A», просто легче запомнить (и научить других) просто сделать это одним способом.
Вам не разрешается (по стандарту C++) перегружать std::swap, однако вам разрешено добавлять специализации шаблонов для ваших собственных типов в пространство имен std. Например.
namespace std
{
template<>
void swap(my_type& lhs, my_type& rhs)
{
// ... blah
}
}
тогда использование в контейнерах std (и в любом другом месте) будет использовать вашу специализацию вместо общей.
Также обратите внимание, что реализация swap в базовом классе недостаточно хороша для ваших производных типов. Например, если у вас есть
class Base
{
// ... stuff ...
}
class Derived : public Base
{
// ... stuff ...
}
namespace std
{
template<>
void swap(Base& lha, Base& rhs)
{
// ...
}
}
это будет работать для базовых классов, но если вы попытаетесь поменять местами два производных объекта, то будет использована общая версия из std, потому что шаблонизированный swap является точным совпадением (и это позволяет избежать проблемы замены только 'базовых' частей ваших производных объектов).
ПРИМЕЧАНИЕ: Я'обновил это, чтобы удалить неправильные биты из моего последнего ответа. D'oh! (спасибо puetzk и j_random_hacker за то, что указали на это).
Хотя правильно, что обычно не следует добавлять что-либо в пространство имен std::, добавление специализаций шаблонов для определяемых пользователем типов разрешено. Перегрузка функций - нет. Это тонкая разница :-)
17.4.3.1/1 Для программы на C++ не определено добавлять объявления или определения в пространство имен std или пространства имен с пространством имен std, если не указано иное. если не указано иное. Программа может добавлять специализации шаблонов для любого стандартного библиотечного шаблона в пространство имен std. Такая специализация (полная или частичная) стандартной библиотеки приводит к неопределенному поведение, если только объявление не зависит от определяемого пользователем имени внешней связи и если специализация шаблона не удовлетворяет требованиям стандартной библиотеки для исходного шаблона.
Специализация std::swap
namespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
Без бита template<> это была бы перегрузка, которая не определена, а не специализация, которая разрешена. Предлагаемый @Wilka' подход изменения пространства имен по умолчанию может сработать в пользовательском коде (из-за того, что Koenig lookup предпочтет версию без пространства имен), но это' не гарантировано, и на самом деле не должно (реализация STL должна использовать полностью квалифицированное std::swap).
Существует тема на comp.lang.c++.moderated с длинным обсуждением этой темы. Большая часть из них посвящена частичной специализации, хотя (в настоящее время нет хорошего способа сделать это).