【问题标题】:Is it possible to simultaneously remove and get a copy of an object from C++ std::vector or std::deque?是否可以同时从 C++ std::vector 或 std::deque 中删除并获取对象的副本?
【发布时间】:2013-11-12 11:13:59
【问题描述】:

在 Java 中,Deque 类的末端有 removal methods,它实际上返回了返回的元素。 在 C++ 中,似乎实现相同行为的唯一方法是首先显式复制元素,然后将其弹出。

std::deque<int> myDeque;
myDeque.push_back(5);

int element = myDeque.back();
myDeque.pop_back();

有没有一种机制可以同时做到这两点?

【问题讨论】:

  • 删除 [java] 因为答案与 Java 无关。

标签: c++ c++11 deque


【解决方案1】:

如果您不想失去异常安全性,可以使用 SFINAE 限制此方法:

// 
// Precondition: !container.empty()
//               
template <typename C>
auto back_popper(C & container)
-> typename std::enable_if<std::is_nothrow_move_constructible<
                             decltype(container.back())>::value,
                           decltype(container.back())>::type
{
    auto val = std::move(container.back());
    container.pop_back();
    return val;
}

static_assert(自己解决)。 (注意:编辑以避免任何副本,以防有移动构造函数,请遵循 Simple 的评论。

【讨论】:

  • 这应该是std::is_nothrow_move_constructible
  • @Simple 是的,但它可能不可移动并且仍然不可复制......允许所有可能性(我认为),请参阅编辑。
  • 如果该类型不可移动,那么std::is_nothrow_move_constructible 将检查它是否因为它的定义方式而不能复制构造。请注意,return std::move(val) 可防止复制省略。你应该写return val
  • 此外,您实际上还需要在第一行进行复制。所以你需要检查两者。
  • 或者只是std::move(container.back())
【解决方案2】:

您可以编写自己的包装函数模板:

// Precondition: !container.empty()
// Exception safety: If there is an exception during the construction of val,
//                   the container is not changed.
//                   If there is an exception during the return of the value,
//                   the value is lost.
template <typename C>
auto back_popper(C & container) -> decltype(container.back())
{
    auto val(std::move(container.back()));
    container.pop_back();
    return val;
}

用法:

auto element = back_popper(myDeque);

您无法解决导致back()pop_back() 分离的根本问题,即元素的复制或移动构造函数可能会抛出异常,并且当这种情况发生时您可能会丢失弹出的元素.您可以通过返回非抛出对象来逐个减轻它,例如unique_ptr,这将权衡丢失元素的风险以进行动态分配,但显然这是您必须做出的个人选择,标准不适合您。

例如:

// Guarantees that you either get the last element or that the container
// is not changed.
//
template <typename C>
auto expensive_but_lossless_popper(C & container)
-> typename std::unique_ptr<decltype(container.back())>
{
    using T = decltype(container.back());

    std::unique_ptr<T> p(new T(std::move(container.back())));
    container.pop_back();
    return p;                // noexcept-guaranteed
}

编辑:按照@Simple 的建议,我在back() 调用周围添加了std::move。这是合法的,因为不再需要 move-from 元素,并且许多现实世界的类都带有 noexcept 移动构造函数,因此这涵盖了大量情况,而“无损”解决方法只为少数情况提供了优势没有 noexcept 动作的“奇怪”类型。

【讨论】:

  • back_popper 这个名字听起来更像是一个类而不是一个函数 IMO。
  • 在我发布答案后,我看到了你的编辑,你用代码和评论解释了它,所以:+1
  • @DanielFrey:我更进一步解释了另一种解决方案:-S
  • 这只是说明为什么没有“标准”方法很重要:没有一刀切的解决方案,您需要自己决定如何处理问题.例如对于使用int 的OP,没有风险,因为int 在复制时从不抛出。
  • 您真的想立即将 new T 返回的值放入 unique_ptr 以确保它永远不会被泄露,因为 pop_back() 不提供 nothrow-guarantee (AFAIK) .
【解决方案3】:

按照设计,C++ 不提供这种开箱即用的机制来确保异常安全

当你尝试实现它时,你首先复制元素,然后弹出容器,最后你想将对象返回给调用者。但是当最后一个操作的复制构造函数抛出异常时会发生什么?该对象不再在容器中,作为调用者的您没有收到它的副本。为了防止这种情况并为容器的操作提供强大的异常保证,返回同时被弹出的元素的操作不会被直接支持。

【讨论】:

  • 在 C++11 中,难道我们没有办法识别承诺不会在复制构造中抛出的对象吗?然后,该库可以为此类元素类型提供复制弹出并返回方法。
  • @Walter 理论上这是可能的,您甚至可以根据std::is_nothrow_copy_constructible&lt;T&gt;::valuepop_back 的返回类型从void 更改为T。或者你可以更进一步,使用移动操作和std::is_nothrow_move_constructible&lt;T&gt;::value。但是正如您所看到的,要考虑的事情的数量(类型是否可复制和/或可移动?)并非微不足道,AFAIK 还没有人写过论文来建议它。
  • @Walter 您的建议在 Anthony Williams 的C++ Concurrency in Action 的第 3.2.3 节中从线程安全的角度进行了更详细的讨论。
猜你喜欢
  • 2012-03-07
  • 1970-01-01
  • 1970-01-01
  • 2013-07-11
  • 1970-01-01
  • 1970-01-01
  • 2015-01-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多