Я предлагаю эту реализацию swap
, если она действительна, превосходит текущую реализацию std::swap
:
#include <new>
#include <type_traits>
template<typename T>
auto swap(T &t1, T &t2) ->
typename std::enable_if<
std::is_nothrow_move_constructible<T>::value
>::type
{
alignas(T) char space[sizeof(T)];
auto asT = new(space) T{std::move(t1)};
// a bunch of chars are allowed to alias T
new(&t1) T{std::move(t2)};
new(&t2) T{std::move(*asT)};
}
страница для std::swap
в cppreference предполагает использование перемещения-назначения, поскольку Спецификация noexcept
зависит от того, является ли назначение перемещения невыбрасываемым. Кроме того, здесь задан вопрос как реализовано swap
, и это что я вижу в реализации для libstdc++ и libc++ а>
template<typename T>
void typicalImplementation(T &t1, T &t2)
noexcept(
std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value
)
{
T tmp{std::move(t1)};
t1 = std::move(t2);
// this move-assignment may do work on t2 which is
// unnecessary since t2 will be reused immediately
t2 = std::move(tmp);
// this move-assignment may do work on tmp which is
// unnecessary since tmp will be immediately destroyed
// implicitly tmp is destroyed
}
Мне не нравится использование перемещения-назначения, как в t1 = std::move(t2)
, потому что это подразумевает выполнение кода для освобождения ресурсов, удерживаемых в t1
, если ресурсы удерживаются, даже если известно, что ресурсы в t1
уже освобождены. У меня есть практический случай, когда освобождение ресурсов происходит при вызове виртуального метода, поэтому компилятор не может устранить эту ненужную работу, потому что он не может знать код виртуального переопределения, каким бы он ни был, ничего не сделает, потому что нет ресурсы для освобождения в t1
.
Если это незаконно на практике, не могли бы вы указать, что нарушает стандарт?
До сих пор я видел в ответах и комментариях два возражения, которые могут сделать это незаконным:
- Эфемерный объект, созданный в
tmp
, не уничтожается, но в пользовательском коде может быть некоторое предположение, что еслиT
будет создан, он будет уничтожен. T
может быть типом с константами или ссылками, которые нельзя изменить, назначение перемещения может быть реализовано путем замены ресурсов без изменения этих констант или повторного связывания ссылок.
Таким образом, кажется, что эта конструкция допустима для любого типа, кроме тех, которые соответствуют случаям 1 или 2 выше.
Для иллюстрации я поместил ссылку на страницу обозревателя компилятора, на которой показаны все три реализации для случая перестановки векторов целых чисел, то есть типичной реализации по умолчанию для std::swap
, специализированной для vector
и той, которую я предлагаю. Вы можете заметить, что предлагаемая реализация выполняет меньше работы, чем типовая, точно такая же, как специализированная в стандарте.
Только пользователь может решить поменять местами выполнение «построения с полным перемещением» и «построение с одним перемещением, задание с двумя перемещениями», ваши ответы информируют пользователей о том, что «построение с полным перемещением» недействительно.
После дополнительных разговоров с коллегами, то, что я спрашиваю, сводится к тому, что это работает для типов, в которых перемещение может рассматриваться как деструктивное, поэтому нет необходимости уравновешивать конструкции разрушениями.
auto lt1 = std::launder(&t1);
делаетlt1
пригодным для доступа к новому объекту, но не влияет наt1
, поэтому использованиеt1
для доступа к новому объекту по-прежнему UB. - person alain   schedule 06.11.2018