代码之家  ›  专栏  ›  技术社区  ›  Xenial

在向量中使用不带复制和无异常移动构造函数的对象。什么东西会断裂?我如何确认?

  •  9
  • Xenial  · 技术社区  · 7 年前

    我已经检查了很多move-constructor/vector/noexcept线程,但我仍然不确定当事情应该出错时会发生什么。我不能在我期望的时候产生错误,所以要么我的小测试是错误的,要么我对问题的理解是错误的。

    我使用的是BufferTrio对象的向量,它定义了一个noexcept(false)move构造函数,并删除了所有其他构造函数/赋值运算符,这样就没有什么可依赖的了:

        BufferTrio(const BufferTrio&) = delete;
        BufferTrio& operator=(const BufferTrio&) = delete;
        BufferTrio& operator=(BufferTrio&& other) = delete;
    
        BufferTrio(BufferTrio&& other) noexcept(false)
            : vaoID(other.vaoID)
            , vboID(other.vboID)
            , eboID(other.eboID)
        {
            other.vaoID = 0;
            other.vboID = 0;
            other.eboID = 0;
        }
    

    https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html :

    只要移动操作不例外,vector在需要增加(或减少)容量时将使用move。

    或来自 优化C++:Kurt Guntheroth提出的经验证的提高性能的技术 :

    如果移动构造函数和移动赋值运算符未声明为noexcept,则std::vector将使用效率较低的复制操作。

    由于我已经删除了这些,我的理解是,这里应该有一些突破。但这个向量运行正常。我还创建了一个基本循环,将_向后推50万次到一个虚拟向量中,然后将该向量与另一个单元素虚拟向量交换。像这样:

        vector<BufferTrio> thing;
    
        int n = 500000;
        while (n--)
        {
            thing.push_back(BufferTrio());
        }
    
        vector<BufferTrio> thing2;
        thing2.push_back(BufferTrio());
    
        thing.swap(thing2);
        cout << "Sizes are " << thing.size() << " and " << thing2.size() << endl;
        cout << "Capacities are " << thing.capacity() << " and " << thing2.capacity() << endl;
    

    输出:

    Sizes are 1 and 500000
    Capacities are 1 and 699913
    

    仍然没有问题,因此:

    我应该看到出了什么问题吗?如果是,我该如何证明?

    3 回复  |  直到 7 年前
        1
  •  10
  •   Kerrek SB    7 年前

    向量重新分配尝试提供异常保证,即如果在重新分配操作期间引发异常,则尝试保持原始状态。有三种情况:

    1. 元素类型为 nothrow_move_constructible

    2. 元素类型为 CopyInsertable nothrow\u move\u可构造 ,这足以提供强有力的保证,尽管副本是在重新分配期间制作的。这是旧的C++03默认行为,是效率较低的回退。

    3. 元素类型既不是 可插入副本 也没有 nothrow\u move\u可构造

    说明这一点的规范性措辞分布在各种重新分配职能中。例如,[向量修饰符]/ push_back 说:

    如果在 在末尾插入单个元素,然后 T 可插入副本 is_nothrow_move_constructible_v<T> true 非- 可插入副本 ,影响未指定。

    我不知道你引用的帖子的作者在想什么,尽管我可以想象他们在暗中假设你想要强大的例外保证,所以他们想引导你进入案例(1)或(2)。

        2
  •  4
  •   Rakete1111    7 年前

    你的例子没有出错。从…起 std::vector::push_back

    如果T的移动构造函数不是 noexcept *this ,vector将使用投掷移动构造函数。如果它抛出,担保将被放弃,其影响将不明确。

    std::vector 首选非抛出移动构造函数,如果没有可用的,则会返回到复制构造函数(抛出或不抛出)。但是如果这也不可用,那么它必须使用投掷移动构造函数。基本上,向量试图避免抛出构造函数并使对象处于不确定状态。

    因此,在这方面,您的示例是正确的,但如果您的移动构造函数实际上抛出了一个异常,那么您将有未指定的行为。

        3
  •  3
  •   AndyG    7 年前

    太长,读不下去了 只要类型是 ,你没事,给你。

    类型是 如果,给定容器的分配器 A ,分配给变量的分配器的实例 m ,指向 T* 打电话 p T 以下表达式格式正确:

    allocator_traits<A>::construct(m, p, rv);
    

    为了您的 std::vector<BufferTrio> ,您使用的是默认值 std::allocator<BufferTrio> ,所以这次通话 construct 电话

    m.construct(p, std::forward<U>(rv))
    

    哪里 U 是转发引用类型(在您的情况下是 BufferTrio&&

    m.construct 将使用placement new就地构造成员([分配器.成员])

    ::new((void *)p) U(std::forward<Args>(args)...)
    

    这在任何时候都不会发生 要求 noexcept . 这只是出于例外保证的原因。

    [向量修饰符]表示 void push_back(T&& x);

    如果移动引发异常 非- CopyInsertable T ,影响未指定。


    最后 关于您的 swap ( ):

    [容器.要求.概述]

    a.swap(b) ,用于容器 a b 标准容器类型,而不是 array ,应交换 价值观 b 元素 .