C++ 移动哪个投掷?

C++ 移动哪个投掷?,c++,exception-handling,move-semantics,C++,Exception Handling,Move Semantics,据我所知,move构造函数和move assign必须标记为noexcept,以便编译器在重新分配向量时使用它们 然而,在现实世界中,是否存在移动赋值、移动构造可能实际抛出的情况 更新: 例如,构建时具有分配资源的类不能是无抛出移动 但是,在现实世界中,是否存在这样的情况:, 移动构造(或交换)实际上可能会抛出 对。考虑 STD::清单的实现。end迭代器必须指向列表中的“最后一个元素的前面”。存在std::list的实现,其中end指向的是一个动态分配的节点。即使是默认构造函数也会分配这样一个

据我所知,move构造函数和move assign必须标记为noexcept,以便编译器在重新分配向量时使用它们

然而,在现实世界中,是否存在移动赋值、移动构造可能实际抛出的情况

更新:

例如,构建时具有分配资源的类不能是无抛出移动

但是,在现实世界中,是否存在这样的情况:, 移动构造(或交换)实际上可能会抛出

对。考虑<代码> STD::清单的实现。
end
迭代器必须指向列表中的“最后一个元素的前面”。存在
std::list
的实现,其中
end
指向的是一个动态分配的节点。即使是默认构造函数也会分配这样一个节点,这样当您调用
end()
时,就有一些东西可以指向

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

这种行为可以扩展到任何基于节点的容器

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

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

更新

下面是一个一致性和可移植的示例,说明了当
在容器上传播\u移动\u赋值::value
为false时的情况。它已经针对最新版本的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';
    }
}

这表明当
propagate_on_container_move_assignment::value
为false且所讨论的两个分配器比较不相等时,
vector
move assignment操作符正在复制/移动其元素。如果这些拷贝/移动中有任何一个抛出,那么容器移动分配抛出。

是的,抛出移动构造函数存在于野外。考虑<代码> STD::配对< /代码> <代码> t>代码>除可移动外,<>代码> u>代码>仅可复制(假设复制可抛出)。然后您就有了一个有用的
std::pair
move构造函数,它可能会抛出

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


另请参见

在具有
const
数据成员也可以抛出的类上移动构造函数。查看更多信息

我对搬家也有同样的想法。但是为什么不进行交换?@B敪敪敪ћ:您需要一个非抛出交换,以从和类似的习惯用法中获得强大的异常保证。流对象的移动构造函数可以抛出。@jrok他们为什么要抛出?呵呵,想一想,我自己在Write类上写了一个副本,它也有一个抛出移动,因为它总是要分配一个对象。嗯,仍然没有抛出Thoub的交换,但是如果你可以偷走你正在移动的对象,为什么你需要分配一个新对象呢?除非你想让对象的移动保持在一个有效的状态,也就是说,但是你真的需要这样做吗?我觉得这些例子不是很有说服力。对于
std::list
,也可以移动动态分配的节点。不需要在移动时进行分配,除非您希望将“从移动”对象保持在某种僵化(部分可用)状态。另一个是程序员错误(无法分配不兼容的分配器),应该使用std::terminate()。@ValentinMilea:请将您的设计提交给Visual Studio和gcc的实现者:
1