Implementing swap() for objects with internal poin

2019-07-15 13:48发布

问题:

I'm implementing the copy-and-swap idiom for operator= of a small non-owning-memory-referencing object I've designed. When MemRef is referencing a piece of a buffer whose lifetime I trust, _ptr points into the buffer, as you'd expect.

What is unusual about this MemRef is that it consists not only of a _ptr and a _len, but also a _memory std::string: there are certain users (or situations) of this class whom I do not trust to protect their memory; for them, I actually copy their memory into the _memory string during construction, and set _ptr = _memory.c_str(). I can always determine whether I have an "inref" MemRef (referring to its internal memory) or an "exref" MemRef (referring to some external buffer) by asking if _ptr == _memory.c_str().

I'm confused about how to write the swap routine. The following is taken from copy-and-swap idiom:

Here's operator=:

MemRef&
MemRef::operator=(MemRef other) { 
    swap(*this, other);
    return *this;
}

Here's the copy constructor:

// 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();
    }
}

And here's my swap(first, second) - which I believe needs more work.

void
swap(MemRef& first, MemRef& second) {
    using std::swap;
    swap(first._memory, second._memory);
    swap(first._ptr, second._ptr);
    swap(first._len, second._len);
}

So if I have:

MemRef mr_a("foo"); // creates an "inref" memref
MemRef mr_b(buffer_ptr, length); // creates an "exref" memref -> "blarch"
mr_a = mr_b;

operator=() gets called with a temporary MemRef built by copy-constructing mr_b; it calls swap(mr_a, mr_b_copy); swap() exchanges the pointer, length, and string (so that mr_a's former contents will be destructed along with mr_b_copy).

What I don't understand is whether the pointers in mr_a and mr_b_copy are correct at this point, or if they're tangled up with each other.

UPDATE 1: The above example doesn't illustrate the problem. Consider this one:

MemRef mr_a;  // creates a memref with _ptr(NULL), _len(0)
mr_a = "woof"; // 

For passing by value to operator=(), a temporary inref is constructed for "woof" and bound to the parameter other. Then, references to mr_a and to other are passed to swap() and bound as first and second respectively. After the swap, first._ptr was ... well, wrong. Pointing to garbage. Here's what I had to do:

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();
    }
}

All I can conclude is that std::swap(string, string) is doing something strange.

回答1:

With the update to the question, I think I can see what the problem is =)

In your initial swap() function, you had these lines:

swap(first._memory, second._memory);
swap(first._ptr, second._ptr);
swap(first._len, second._len);

If these were exrefs, all was well and good - swap would be executed as planned. However, if one was an inref (for now, we'll use the example you provided), then this was going on:

In these lines:

MemRef mr_a;  // creates a memref with _ptr(NULL), _len(0)
mr_a = "woof";

as you said correctly, a temporary inref was created from "woof", with its _ptr variable pointing to the beginning of _memory.c_str().

Now, when your swap() gets called, first this happens:

swap(first._memory, second._memory);

So far all good. You've swapped the strings - but their addresses haven't changed, only their contents. From the standard:

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()... C++ International Standard n1905

So now, during the line

swap(first._ptr, second._ptr);

you introduce the prolem. Those are pointing to somewhere undefined - by swapping the string, we invalidate any pointers/iterators/references to the string or its members, including c_str() - but we definitely don't swap the nemory locations. So, swapping the pointers is wrong in this case, as you realized.

Fortunately, you've already solved the problem - by resetting the pointers in the case of an inref instead of swapping them, you avoid pointing to invalidated memory locations, and all problems are resolved! Hopefully this clears up what was going on though =)

edit: added standard reference and clarified!



标签: c++ swap