Xirema
Xirema

Reputation: 20396

When do I need a swap function?

When I write classes that hold resources, I'm very accustomed to writing simple swap functions to simplify the process of transferring/copying resources. The following code is a trivial example:

class MyArray {
    public:
    MyArray(size_t size) :
    _array(size ? new int[size] : nullptr),
    _size(size)
    {
    }

    MyArray(const MyArray & mv) :
    MyArray(mv._size)
    {
        if(mv._size) std::copy(mv._array, mv._array + mv._size, _array);
    }

    static void friend swap(MyArray & mv1, MyArray & mv2) noexcept {
        std::swap(mv1._array, mv2._array);
        std::swap(mv1._size, mv2._size);
    }

    MyArray(MyArray && mv) {
        swap(*this, mv);
    }

    MyArray & operator=(MyArray mv) {
        swap(*this, mv);
        return *this;
    }

    ~MyArray() noexcept {
        delete[] _array;
    }

    int & operator[](size_t index) {
        if(index >= _size) throw std::exception("Index Out of Bounds!");
        return _array[index];
    }

    const int & operator[](size_t index) const {
        if(index >= _size) throw std::exception("Index Out of Bounds!");
        return _array[index];
    }

    size_t size() const {
        return _size;
    }
private:
    int * _array;
    size_t _size;
};

However, I could have chosen to [equivalently] implement the same code like so:

class MyArray {
    public:
    MyArray(size_t size) :
    _array(size)
    {
    }

    int & operator[](size_t size) {
        if(index >= size()) throw std::exception("Index Out of Bounds!");
        return _array[index];
    }

    const int & operator[](size_t index) const {
        if(index >= size()) throw std::exception("Index Out of Bounds!");
        return _array[index];
    }

    size_t size() const {
        return _array.size();
    }

private:
    std::vector<int> _array;
};

And in the second code, the swap functionality defaults to the normal std::swap behavior (i.e. move-construct the first into the temporary, move-assign the second into the first, and move-assign the temporary into the second), which should be identical (on a design level) to the first code.

The question I have then, is what would be a scenario where I must explicitly define the swap function, but isn't specifically a scenario where I'm just trying to reuse code (like from the first example code)? Are there obvious (or not-so-obvious) scenarios where, even though nothing in my code is explicitly managing a resource, I will objectively benefit from a swap function? Should I just auto-write a swap function for every [move-constructable] class I write, or will I just waste code by doing that?

Upvotes: 4

Views: 372

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275360

If you don't define your own swap operation, using std::swap; swap(lhs, rhs); compiles to roughly this:

auto tmp = std::move(rhs);
rhs = std::move(lhs);
lhs = std::move(tmp);

this causes 3 moves to occur, and requires a compete temporary object to exist.

If the move assign and move construct operator has the strong exception guarantee, this swap has a weak exception guarantee. If the move assign and move construct operator is nothrow, so is this swap.

In comparison, member-wise swap can leave objects in incoherent states. If your code can handle this, it means you never have to create a full temporary object. So if you can handle partially swapped components, and full temporary objects (created from a move) have some cost, then a member-wise swap could be faster.

However, if everything is nothrow (which swap / move usually is), the resulting swap operations are logically equivalent.


Your second MyArray doesn't actually manage resources. That is handled by the std::vector, which did write most of the code in your first one.

The rule of Zero is that classes that are not managing resources do not need to write any of the copy or move assignment or construction, nor the destructor. Such classes probably do not need swap either.

Classes that do write copy/move and destruction, ones that manage resources, may use swap to do so. Otherwise, std::swap does a decent job.


As an aside, if they do use swap, and the swap is simply memberwise, write this:

friend auto mytie( MyArray& self ) noexcept {
  return std::tie(self.array, self.size);
}

then swap becomes:

friend void swap( MyArray& lhs, MyArray& rhs ) noexcept {
  std::swap( mytie(lhs), mytie(rhs) );
}

which reduces your "repeat ever member name" boilerplate count by 1.

Upvotes: 5

Related Questions