Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move which throws?

To my understanding, move-constructors and move-assign must be marked noexcept in order for the compiler to utilize them when, for example, reallocating inside a vector.

However, is there any real-world case where a move-assign, move-construct might actually throw?

Update:

Classes that for example has an allocated resource when constructed cant be no-throw move.

like image 213
Viktor Sehr Avatar asked Nov 07 '13 10:11

Viktor Sehr


2 Answers

However, is there any real-world case where a move-assign, move-construct (or swap) might actually throw?

Yes. Consider an implementation of std::list. The end iterator must point "one past the last element" in the list. There exist implementations of std::list where what end points to is a dynamically allocated node. Even the default constructor allocates such a node so that when you call end(), there is something to point to.

In such an implementation, every constructor must allocate a node for end() to point to… even the move constructor. That allocation may fail, and throw an exception.

This same behavior can extend to any node-based container.

There are also implementations of these node-based containers that do a "short-string" optimization: They embed the end node within the container class itself, instead of dynamically allocating. Thus the default constructor (and move constructor) need not allocate anything.

The move assignment operator can throw for any container<X> if for the container's allocator propagate_on_container_move_assignment::value is false, and if the allocator in the lhs is not equal to the allocator in the rhs. In that case the move assignment operator is forbidden from transferring memory ownership from the rhs to the lhs. This can not happen if you are using std::allocator, as all instances of std::allocator are equal to one another.

Update

Here is a conforming and portable example of the case when propagate_on_container_move_assignment::value is false. It has been tested against the latest version of VS, gcc and clang.

#include <cassert>
#include <cstddef>
#include <iostream>
#include <vector>

template <class T>
class allocator
{
    int id_;
public:
    using value_type    = T;

    allocator(int id) noexcept : id_(id) {}
    template <class U> allocator(allocator<U> const& u) noexcept : id_(u.id_) {}

    value_type*
    allocate(std::size_t n)
    {
        return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
    }

    void
    deallocate(value_type* p, std::size_t) noexcept
    {
        ::operator delete(p);
    }

    template <class U, class V>
    friend
    bool
    operator==(allocator<U> const& x, allocator<V> const& y) noexcept
    {
        return x.id_ == y.id_;
    }
};

template <class T, class U>
bool
operator!=(allocator<T> const& x, allocator<U> const& y) noexcept
{
    return !(x == y);
}

template <class T> using vector = std::vector<T, allocator<T>>;

struct A
{
    static bool time_to_throw;

    A() = default;
    A(const A&) {if (time_to_throw) throw 1;}
    A& operator=(const A&) {if (time_to_throw) throw 1; return *this;}
};

bool A::time_to_throw = false;

int
main()
{
    vector<A> v1(5, A{}, allocator<A>{1});
    vector<A> v2(allocator<A>{2});
    v2 = std::move(v1);
    try
    {
        A::time_to_throw = true;
        v1 = std::move(v2);
        assert(false);
    }
    catch (int i)
    {
        std::cout << i << '\n';
    }
}

This program outputs:

1

which indicates that the vector<T, A> move assignment operator is copy/moving its elements when propagate_on_container_move_assignment::value is false and the two allocators in question do not compare equal. If any of those copies/moves throws, then the container move assignment throws.

like image 93
Howard Hinnant Avatar answered Nov 02 '22 13:11

Howard Hinnant


Yes, throwing move constructors exist in the wild. Consider std::pair<T, U> where T is noexcept-movable, and U is only copyable (assume that copies can throw). Then you have a useful std::pair<T, U> move constructor which may throw.

There is a std::move_if_noexcept utility in the standard library if you need (useful to implement std::vector::resize with at least the basic exception guarantee).

See also Move constructors and the Strong Exception Guarantee

like image 28
Alexandre C. Avatar answered Nov 02 '22 13:11

Alexandre C.