【问题标题】:Nesting std::containers of movable objects嵌套可移动对象的 std::containers
【发布时间】:2020-07-30 20:18:31
【问题描述】:

我有一个班级NoCopy可移动,但不可复制

我需要制作一个 NoCopy 的 3 个队列的向量。我可以创建一个空的,但是没有办法添加任何元素。

我可以创建一个std::vector<NoCopy>std::queue<NoCopy> 并填充它们。但不适用于std::vector<std::queue<NoCopy>>

MWE:

#include <iostream>
#include <vector>
#include <queue>

class NoCopy{
public:
    NoCopy() = default;
    NoCopy& operator = (const NoCopy&) = delete;
    NoCopy(const NoCopy&) = delete;

    NoCopy(NoCopy&&) = default;
    NoCopy& operator = (NoCopy&&) = default;

};
using QNC = std::queue<NoCopy>;

int main(void) {
    QNC q;
    q.push(std::move(NoCopy()));

    std::vector<NoCopy> ncvec;
    ncvec.emplace_back();

    std::cout << "Queue size " << q.size() << ", vector size: " << ncvec.size() << std::endl;

    std::vector<QNC> qvec;
    //????

    return 0;
}

有什么想法吗?

【问题讨论】:

  • 您可以从三个空队列的向量开始,然后填充它们或将它们与现有队列交换:std::vector&lt;QNC&gt; qvec(3); qvec[0].swap(q); qvec[1].push(NoCopy());
  • 问题似乎是vector需要noexcept移动语义,而std::queue不会从底层值传播自己的移动语义'noexcept-ness
  • @SamVarshavchik Ditto. this workaround 合法吗?
  • 可以使用ncvec.emplace_back(q),emplace_back()使用移动语义。
  • @SamVarshavchik:它确实传播了这一点,但这还不够好;我写了一个答案。

标签: c++ containers movable


【解决方案1】:

默认情况下,std::queue 基于std::deque,不保证nothrow-movable。其他合适的标准容器std::list 也不是;这些规则允许始终分配至少一个节点/块的实现。 std::vector 在移动可能抛出时使用副本重新分配(以保证异常安全),除非该类型根本不可复制,并且这两个容器也不会从其元素类型传播 non-copyability但如果你尝试就会失败。最后一个可以说是标准中的一个缺陷,因为对这种传播的期望不断提高,但修复它与对 incomplete 类型的支持不兼容:

struct Node {
  std::vector<Node> children;
  // …
};

请注意,libstdc++ 和 libc++确实 都使这两个容器不可移动(在每种情况下都是从版本 9 开始),这是允许的扩展,但 MSVC 的 STL 不允许。

您仍然可以使用std::vector&lt;QNC&gt; v(3);;构造函数“知道”重新分配从来没有必要。或者您可以提供一个不可复制或不可移动的包装器例如,一个派生自std::queue 的类);前者将放弃std::vector 的异常安全性,而后者将调用std::terminate 如果移动底层容器确实抛出。

【讨论】:

  • 感谢您的严谨分析,非常感谢!最终,std::vector&lt;QNC&gt; v(3); 为我工作,我希望我永远不必调整矢量的大小,否则我将不得不包装它。我没有弄清楚这一点的一个原因值得一提:我将std::vector&lt;QNC&gt; qvec 作为另一个类的成员变量,并尝试在头文件中默认初始化它。这种初始化只允许{x};=x;初始化,如果结果是非初始化列表调用,我倾向于避免使用大括号std::vector&lt;QNC&gt; qvec {3};
  • @OndřejNavrátil:谨慎使用列表初始化是有意义的。它可能有点冗长,但你总是可以在课堂上写T mem=T(3);;从 C++17 开始,这甚至不需要可移动性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
相关资源
最近更新 更多