Я не мог спать прошлой ночью и начал думать о std::swap
.Вот знакомая версия C ++ 98:
template <typename T>
void swap(T& a, T& b)
{
T c(a);
a = b;
b = c;
}
Если пользовательский класс Foo
использует внешние ресурсы, это неэффективно.Обычная идиома - предоставить метод void Foo::swap(Foo& other)
и специализацию std::swap<Foo>
.Обратите внимание, что это не работает с классами templates , поскольку вы не можете частично специализировать шаблон функции, а перегрузка имен в пространстве имен std
недопустима.Решение состоит в том, чтобы написать шаблонную функцию в собственном пространстве имен и полагаться на поиск в зависимости от аргумента, чтобы найти его.Это критически зависит от клиента, который будет следовать идиоме using std::swap
вместо прямого вызова std::swap
.Очень хрупкий.
В C ++ 0x, если Foo
имеет определяемый пользователем конструктор перемещения и оператор присваивания перемещения, предоставляя пользовательский метод swap
и специализация std::swap<Foo>
практически не имеет производительностиПреимущество, потому что версия C ++ 0x std::swap
использует эффективные ходы вместо копий:
#include <utility>
template <typename T>
void swap(T& a, T& b)
{
T c(std::move(a));
a = std::move(b);
b = std::move(c);
}
Больше не нужно возиться с swap
, и это уже отнимает у программиста большую нагрузку.Текущие компиляторы еще не генерируют конструкторы перемещения и операторы присваивания перемещения автоматически, но, насколько я знаю, это изменится.Единственная оставшаяся проблема - безопасность исключений, потому что в общем случае операции перемещения разрешены, и это открывает целую банку червей.Вопрос "Каково состояние перемещенного объекта?"еще более усложняет ситуацию.
Тогда я подумал, какова семантика std::swap
в C ++ 0x, если все идет хорошо?Каково состояние объектов до и после обмена?Как правило, замена с помощью операций перемещения не затрагивает внешние ресурсы, а только сами «плоские» представления объектов.
Так почему бы просто не написать шаблон swap
, который выполняет именно это: меняет местами представления объектов ?
#include <cstring>
template <typename T>
void swap(T& a, T& b)
{
unsigned char c[sizeof(T)];
memcpy( c, &a, sizeof(T));
memcpy(&a, &b, sizeof(T));
memcpy(&b, c, sizeof(T));
}
Это так же эффективно, как и получается: просто гремит через сырую память.Это не требует какого-либо вмешательства со стороны пользователя: не нужно определять никаких специальных методов обмена или операций перемещения.Это означает, что он работает даже в C ++ 98 (который не имеет ссылок на значения), заметьте).Но что еще более важно, теперь мы можем забыть о проблемах безопасности исключений , потому что memcpy
никогда не выбрасывает.
Я вижу две потенциальные проблемы с этим подходом:
* 1040Во-первых, не все объекты предназначены для обмена.Если конструктор класса скрывает конструктор копирования или оператор присваивания копии, попытка поменять объекты класса не удастся во время компиляции.Мы можем просто ввести некоторый мертвый код, который проверяет, допустимо ли копирование и присваивание для типа:
template <typename T>
void swap(T& a, T& b)
{
if (false) // dead code, never executed
{
T c(a); // copy-constructible?
a = b; // assignable?
}
unsigned char c[sizeof(T)];
std::memcpy( c, &a, sizeof(T));
std::memcpy(&a, &b, sizeof(T));
std::memcpy(&b, c, sizeof(T));
}
Любой приличный компилятор может легко избавиться от мертвого кода.(Возможно, есть более эффективные способы проверки «соответствия свопа», но это не главное. Важно то, что это возможно).
Во-вторых, некоторые типы могут выполнять «необычные» действия в конструкторе копирования иоператор копирования копирования.Например, они могут уведомить наблюдателей об их изменении.Я считаю это незначительной проблемой, потому что такие типы объектов, вероятно, не должны были обеспечивать операции копирования.
Пожалуйста, дайте мне знать, что вы думаете об этом подходе к обмену.Будет ли это работать на практике?Вы бы использовали это?Можете ли вы определить типы библиотек, где это может сломаться?Видите ли вы дополнительные проблемы?Обсудить!