Реализация 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: добавлена ​​стандартная ссылка и уточнено!