Как правильно использовать std :: swap:
using std::swap;
swap(a,b);
Это немного многословно, но оно гарантирует, что если a, b определено лучше, то выбрано.
Итак, теперь мой вопрос почему std::swap
не реализован с использованием этой техники, поэтому пользовательский код просто нужно вызвать std::swap
?
Так как то так (игнорируя noexcept
и ограничения для краткости):
namespace std {
namespace internal {
template <class T> // normal swap implementation
void swap(T& a, T& b) { // not intended to be called directly
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
}
template <class T>
void swap(T& a, T& b) {
using internal::swap;
swap(a,b);
}
}
Это входит в тавтологическую территорию, но так не происходит, потому что это не ее цель.
Цель std::swap
должен быть функцией своп последней инстанции. Это не Предполагается, что это то, что вы называете напрямую, если только вы действительно не хотите использовать своп последней надежды.
Теперь вы можете утверждать, что то, что вы предлагаете, является лучшей парадигмой для точек настройки. Задним числом всегда 20/20; не все, что сделал STL, было правильной идеей (см. vector<bool>
).
Что касается того, почему мы не можем изменить это сейчас, это другое дело. std::swap
это функция обмена последней инстанции. Так что теоретически возможно, что люди называют это и ожидая это обойти любой пользовательский код подкачки. И поэтому его изменение нарушает их код.
Вот одна проблема с этим подходом. ADL «двухэтапный» опирается на функцию, которая ADL считает более подходящей, чем обычная функция (в противном случае вы получите ошибку разрешения перегрузки). Это хорошо в большинстве случаев, так как, когда вы пишете swap()
для ваших пользовательских типов в ваших пользовательских пространствах имен вы пишете функции, специфичные для этих типов.
Но для стандартных типов библиотек это может оказаться неэффективным swap()
чем простой алгоритм, это ломается. Пример:
namespace N {
namespace internal {
template <typename T>
void swap(T&, T&); // normal swap impl
}
template <typename T>
void swap(T& a, T& b) {
using internal::swap;
swap(a, b); // (*)
}
struct C { };
}
N::C c;
swap(c, c); // error
N::swap(c, c); // error
Оба эти вызова терпят неудачу по одной и той же причине. Неквалифицированный призыв к swap()
отмеченный (*)
найду N::internal::swap()
через обычный неквалифицированный поиск, а затем найти N::swap()
через ADL. Невозможно провести различие между этими вызовами, так как вам очень нужно, чтобы оба вызова работали для всех типов, которые соответствуют swap
ограничения. Результирующий вызов неоднозначен.
Таким образом, такой дизайн потребует добавления нового swap()
функция для каждого типа в namespace std
даже если бы он просто std::internal::swap()
,
Исторически сложилось так, что мысли о разрешении имен не так много. std::swap
был спроектирован как точка настройки, но также оказался функцией, которую можно было бы вызывать в общем коде для обмена объектами. Следовательно, если std::swap
не работал или был слишком медленным, тогда, возможно, пришлось бы его перегрузить, даже если уже был совершенно хороший swap
быть найденным с ADL. Этот дизайн не может быть изменен без нарушения или изменения значения существующего кода. Теперь были случаи, когда комитет с радостью решил изменить значение существующего кода ради производительности, например, неявную семантику перемещения (например, при передаче временных значений по значению или RVO, где elision не реализована). Так что это не совсем соответствует. Тем не менее, изменение std::swap
от точки настройки до оболочки разрешения имен со всеми существующими std::swap
перегрузки в существовании сомнительны. Это может привести к возникновению катастрофических ошибок в плохо написанном устаревшем коде.
Другая важная причина, IMO, что вы не можете перенести эту идиому в std
Пространство имен, сохраняя его общность. Например.:
namespace A { struct X{}; }
namespace B {
using std::swap;
void swap(A::X&, A::X&);
template<typename T>
void reverse(T (&ts)[4])
{
swap(ts[0], ts[3]);
swap(ts[1], ts[2]);
}
void silly(A::X (&xs)[4])
{
reverse(xs);
}
}
Вот, silly
заводится с помощью B::swap
, Причина этого в том, что std::swap
(с помощью using
) а также B::swap
оба видны с одинаковым приоритетом, но последний лучше подходит. Теперь вы можете утверждать, что это глупый пример, так что вот еще один, который немного менее надуманен:
namespace types { /*...*/ }
namespace algorithms { /*including some swap implementations for types from above...*/ }
template<typename T>
void reverse(T (&ts)[4])
{
using std::swap;
using algorithms::swap;
swap(ts[0], ts[3]);
swap(ts[1], ts[2]);
}
Это будет использовать функцию обмена из algorithms
если это лучший матч, чем любой std::swap
перегрузка, но algorithms::swap
не будет найден ADL, поэтому поиск не может произойти внутри std::swap
в общем. Здесь может быть задействовано произвольное количество пространств имен.