Почему std :: swap не использует идиому swap?

Как правильно использовать 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);
}
}

7

Решение

Это входит в тавтологическую территорию, но так не происходит, потому что это не ее цель.

Цель std::swap должен быть функцией своп последней инстанции. Это не Предполагается, что это то, что вы называете напрямую, если только вы действительно не хотите использовать своп последней надежды.

Теперь вы можете утверждать, что то, что вы предлагаете, является лучшей парадигмой для точек настройки. Задним числом всегда 20/20; не все, что сделал STL, было правильной идеей (см. vector<bool>).

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

6

Другие решения

Вот одна проблема с этим подходом. 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(),

3

Исторически сложилось так, что мысли о разрешении имен не так много. 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в общем. Здесь может быть задействовано произвольное количество пространств имен.

2
По вопросам рекламы [email protected]