Я не мог спать прошлой ночью и начал думать о 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>
. Обратите внимание, что это не работает с шаблонами классов, поскольку вы не можете частично специализировать шаблон функции, а перегрузка имен в пространстве имен std
недопустима. Решение состоит в том, чтобы написать шаблонную функцию в своем собственном пространстве имен и полагаться на поиск, зависящий от аргумента, чтобы найти ее. Это критически зависит от того, будет ли клиент следовать «идиоме using std::swap
» вместо прямого вызова std::swap
. Очень хрупкий.
В C++0x, если Foo
имеет определяемый пользователем конструктор перемещения и оператор присваивания перемещения, предоставление пользовательского метода swap
и специализации std::swap<Foo>
практически не дает выигрыша в производительности, поскольку версия std::swap
для C++0x вместо этого использует эффективные перемещения. копий:
#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 (в котором, заметьте, нет ссылок на rvalue). Но что еще более важно, теперь мы можем забыть о проблемах с безопасностью исключений, потому что memcpy
никогда не выдает ошибки.
Я вижу две потенциальные проблемы с этим подходом:
Во-первых, не все объекты предназначены для замены. Если разработчик класса скрывает конструктор копирования или оператор присваивания копии, попытка поменять местами объекты класса должна завершиться ошибкой во время компиляции. Мы можем просто ввести некоторый мертвый код, который проверяет, разрешены ли копирование и присваивание для типа:
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));
}
Любой приличный компилятор может тривиально избавиться от мертвого кода. (Возможно, есть лучшие способы проверить «соответствие свопа», но это не главное. Важно то, что это возможно).
Во-вторых, некоторые типы могут выполнять «необычные» действия в конструкторе копирования и операторе присваивания копии. Например, они могут уведомить наблюдателей о своем изменении. Я считаю это незначительной проблемой, потому что такие объекты, вероятно, не должны были обеспечивать операции копирования в первую очередь.
Пожалуйста, дайте мне знать, что вы думаете об этом подходе к обмену. Будет ли это работать на практике? Вы бы использовали его? Можете ли вы определить типы библиотек, где это может сломаться? Вы видите дополнительные проблемы? Обсуждать!
std::swap
в любом случае будут иметь лучшие решения с использованием семантики перемещения. - person aschepler   schedule 02.02.2011swap(*polymorphicPtr1,*polymorphicPtr2)
... ваша функция подкачки также поменяет виртуальную таблицу обоих объектов... что вызовет хаос, если кто-то вызовет виртуальную функцию перед вызовом подкачки. - person smerlin   schedule 02.02.2011std::is_polymorphic
. - person fredoverflow   schedule 02.02.2011