Реализация swap() для объектов с внутренними указателями
Я реализую идиому копирования и свопинга для operator=
небольшого объекта, не связанного с владением памятью, который я разработал. Когда MemRef
ссылается на кусок буфера, чье жизненное время я доверяю, _ptr
указывает на буфер, как и следовало ожидать.
Что необычно в этом MemRef
заключается в том, что он состоит не только из _ptr
и a _len
, но также и _memory
std::string
: есть определенные пользователи (или ситуации) этого класса, Я не верю, чтобы защитить их память; для них я фактически копирую свою память в строку _memory
во время построения и устанавливаю _ptr = _memory.c_str()
. Я всегда могу определить, есть ли у меня "inref" MemRef (ссылаясь на его внутреннюю память) или "exref" MemRef (ссылаясь на некоторый внешний буфер), спрашивая, есть ли _ptr == _memory.c_str()
.
Я смущен тем, как писать процедуру подкачки. Из идиомы копирования и свопинга взято следующее:
Здесь operator=
:
MemRef&
MemRef::operator=(MemRef other) {
swap(*this, other);
return *this;
}
Здесь конструктор копирования:
// Copy ctor
MemRef::MemRef(const MemRef& other) :
_memory(other._memory),
_ptr(other._ptr),
_len(other._len)
{ // Special provision if copying an "inref" MemRef
if (other._ptr == other._memory.c_str()) {
_ptr = _memory.c_str();
}
}
И вот мой swap(first, second)
- который, мне кажется, требует больше работы.
void
swap(MemRef& first, MemRef& second) {
using std::swap;
swap(first._memory, second._memory);
swap(first._ptr, second._ptr);
swap(first._len, second._len);
}
Итак, если у меня есть:
MemRef mr_a("foo"); // creates an "inref" memref
MemRef mr_b(buffer_ptr, length); // creates an "exref" memref -> "blarch"
mr_a = mr_b;
operator=()
вызывается с временным MemRef, созданным копированием mr_b; он вызывает swap(mr_a, mr_b_copy);
swap()
обменивает указатель, длину и строку (так что прежнее содержимое mr_a будет разрушено вместе с mr_b_copy).
Я не понимаю, являются ли указатели в mr_a и mr_b_copy правильными в этой точке, или если они запутались друг с другом.
ОБНОВЛЕНИЕ 1. Приведенный выше пример не иллюстрирует проблему. Рассмотрим это:
MemRef mr_a; // creates a memref with _ptr(NULL), _len(0)
mr_a = "woof"; //
Для передачи по значению оператору =() временный inref создается для "woof" и привязан к параметру other
. Затем ссылки на mr_a
и other
передаются в swap() и связаны как first
и second
соответственно. После обмена, first._ptr
был... ну, неправильно. Указывает на мусор. Вот что я должен был сделать:
void
swap(MemRef& first, MemRef& second) {
using std::swap;
// second is an exref
if (second._ptr != second._memory.c_str()) {
swap(first._memory, second._memory);
swap(first._len, second._len);
swap(first._ptr, second._ptr);
}
// second is an inref
else {
swap(first._memory, second._memory);
swap(first._len, second._len);
first._ptr = first._memory.c_str();
}
}
Я могу заключить, что std:: swap (string, string) делает что-то странное.
Ответы
Ответ 1
С обновлением вопроса, я думаю, я вижу, что проблема =)
В вашей начальной функции swap()
у вас были следующие строки:
swap(first._memory, second._memory);
swap(first._ptr, second._ptr);
swap(first._len, second._len);
Если бы это были exrefs, все было хорошо и хорошо - swap был бы выполнен, как и планировалось. Однако, если бы кто-то был inref (на данный момент мы будем использовать пример, который вы указали), то это происходит:
В этих строках:
MemRef mr_a; // creates a memref with _ptr(NULL), _len(0)
mr_a = "woof";
как вы сказали правильно, временный inref был создан из "woof" с переменной _ptr
, указывающей на начало _memory.c_str()
.
Теперь, когда ваш swap()
получает вызов, сначала это происходит:
swap(first._memory, second._memory);
Пока все хорошо. Вы поменяли строки, но их адреса не изменились, а только их содержимое. Из стандарта:
References, pointers, and iterators referring to the elements of a
basic_string sequence may be invalidated by the following uses of that
basic_string object: — As an argument to non-member functions swap()...
Международный стандарт С++ n1905
Итак, теперь во время строки
swap(first._ptr, second._ptr);
вы вводите пролет. Они указывают на где-то undefined - путем замены строки, мы invalidate любые указатели/итераторы/ссылки на строку или ее элементы, включая c_str(), но мы определенно не меняйте местами памяти. Таким образом, замена этих указателей неверна в этом случае, как вы поняли.
К счастью, вы уже решили проблему - сбросив указатели в случае inref вместо их замены, вы избегаете указывать на недействительные ячейки памяти, и все проблемы решены! Надеюсь, это очистит то, что происходило, хотя =)
edit: добавлена стандартная ссылка и уточнено!