移动哪个投掷?

问题描述 投票:6回答:3

据我所知,move-constructors和move-assign必须标记为noexcept,以便编译器在例如在向量内重新分配时使用它们。

但是,是否存在任何真实情况,其中move-assign,move-construct可能实际抛出?

更新:

例如,当构造不能进行无掷移动时,具有已分配资源的类。

c++ exception-handling move-semantics
3个回答
8
投票

但是,是否存在任何真实情况,其中move-assign,move-construct(或swap)可能实际抛出?

是。考虑一下std::list的实现。 end迭代器必须指向列表中的“一个超过最后一个元素”。存在std::list的实现,其中end指向的是动态分配的节点。即使是默认构造函数也会分配这样一个节点,这样当你调用end()时,有一些东西要指向。

在这样的实现中,每个构造函数都必须为end()分配一个节点以指向...甚至移动构造函数。该分配可能会失败,并抛出异常。

此相同的行为可以扩展到任何基于节点的容器。

还有一些基于节点的容器的实现,它们进行“短串”优化:它们将端节点嵌入容器类本身,而不是动态分配。因此默认构造函数(和移动构造函数)不需要分配任何东西。

如果容器的分配器container<X>为false,并且lhs中的分配器不等于rhs中的分配器,则移动赋值运算符可以抛出任何propagate_on_container_move_assignment::value。在这种情况下,禁止移动赋值运算符将内存所有权从rhs转移到lhs。如果您使用std::allocator,则不会发生这种情况,因为std::allocator的所有实例都彼此相等。

更新

propagate_on_container_move_assignment::value为假时,这是一个符合要求的便携式例子。它已经过最新版VS,gcc和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';
    }
}

该计划输出:

1

这表明vector<T, A>移动赋值运算符在propagate_on_container_move_assignment::value为false时复制/移动其元素,并且所讨论的两个分配器不比较相等。如果这些副本/移动中的任何一个抛出,则容器移动分配抛出。


5
投票

是的,投掷移动构造函数存在于野外。考虑std::pair<T, U>,其中T是noexcept-movable,而U只是可复制的(假设副本可以抛出)。然后你有一个有用的std::pair<T, U>移动构造函数可能会抛出。

如果需要,标准库中有一个std::move_if_noexcept实用程序(用于实现至少具有基本异常保证的std::vector::resize)。

另见Move constructors and the Strong Exception Guarantee


0
投票

使用const数据成员移动构造函数也可以抛出。查看here了解更多信息。

© www.soinside.com 2019 - 2024. All rights reserved.