Move which throws?

后端 未结 3 804
既然无缘
既然无缘 2021-01-05 07:55

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.

相关标签:
3条回答
  • 2021-01-05 08:42

    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

    0 讨论(0)
  • 2021-01-05 08:48

    Move constructors on classes with const data members can also throw. Check here for more info.

    0 讨论(0)
  • 2021-01-05 08:49

    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.

    0 讨论(0)
提交回复
热议问题