【问题标题】:Is it safe to move from the top/front of a STL stack/queue?从 STL 堆栈/队列的顶部/前面移动是否安全?
【发布时间】:2020-08-11 13:06:53
【问题描述】:

这个问题适用于堆栈和队列,但为了简单起见,我将在这里仅提及堆栈。

假设我们将非常量对象推入std::stack,当我们从堆栈中弹出时,在弹出之前将堆栈顶部的对象移动到临时变量中是否安全,如下所示:

std::stack<std::string> st;
st.emplace("asdf");
auto popped = std::move(st.top());
st.pop(); 

【问题讨论】:

  • @AlanBirtles 哦,我会从我的问题中编辑那部分。
  • @rafix07 在这种情况下,为什么我需要为stack 定义或派生任何东西?既然top()返回一个非常量引用,我们就不能简单地使用std::move将顶部对象转换为一个x值并使用移动赋值运算符或构造函数(在上述情况下)来初始化popped吗?
  • 我认为 stack::pop 实际上是空堆栈上的未定义行为。我假设因为 here 它被写入 Exception Safety : 提供与在底层容器对象上执行的操作相同级别的保证。 默认情况下,容器是 std::deque (ref) .这一切都意味着stack::pop 在内部调用deque::pop_back。当deque 为空时,here 被写入具有未定义的行为。
  • 继续我之前的评论...当您在只有一个元素的std::stack::top 上使用std::move 时,调用stack::pop 的语句肯定会是一种未定义的行为。它没有说明移动堆栈顶部的正确性。它只是告诉当堆栈只有一个元素并且你知道你会弹出它时这样做是多么错误!
  • @brc-dd std::move 不会破坏源对象。它仍然存在,尽管处于未定义(但有效)的状态。所以,后续pop 是有效的(通常是必要的!)操作,它破坏了std::move 留下的“僵尸”对象。

标签: c++ stack move


【解决方案1】:

是的,如果您使用堆栈的非常量版本,它是安全的。 (对于 const 版本的堆栈,您很可能会复制对象或出现编译错误)

由于std::stack 返回一个对对象的非常量引用,您可以随意修改它。这包括从对象中移出。

回想一下,从一个对象移动会使它保持在有效状态(至少,如果类被正确实现的话)。它不会 摧毁它。它仍然在您的堆栈顶部。它只是不包含任何确定的价值。后续pop 将调用适当的析构函数,没有问题。

注意,对于std::priority_queue不允许搬出,因为这种容器实际上关心内容。并且 - 出于这个原因,它只返回一个 const-reference,它不能用于将内容从中移出。


为了回应 Iamanon 的观察,std::move 可以在 const 引用上执行。事实上,您可以做到这一点。通常它没有用,它通常会减少到副本,而不是移动。考虑以下示例:

#include <iostream>
#include <string>
#include <stack>

class Foo {
    public:
    Foo() {
        std::cout << "created\n";
        }
    ~Foo() {
        std::cout << "destroyed  " << value << "\n";
    }
    Foo(const Foo& other) : value(other.value) {
        std::cout << "copied\n";
    }
    Foo(Foo&& other) : value(other.value) {
        other.value++;
        std::cout << "moved\n";
    }
    Foo(const Foo&& other) : value(other.value) {
        std::cout << "const-moved\n";
    }    
    int value = 0;
};

int main()
{
std::stack<Foo> st;
st.emplace();
const std::stack<Foo>& cst = st;
auto popped = std::move(cst.top());
st.pop();
}

如果您运行上述程序,将使用 const-moved 版本。然而,当你实现你的 const-move 构造函数时,你会意识到你真的不能比你的常规复制构造函数做得更好。那是因为other 保持不变。例如,您不能拥有任何 other 持有的所有权,并在 other 内重置它。

如果你删除了 const-move 构造函数,编译器将使用常规的复制构造函数。

如果复制构造函数被删除,并且只提供了移动构造函数 - 那么编译器会向你抛出一个错误。

【讨论】:

  • 这个讨论让我思考,我们什么时候才能真正拥有const 堆栈对象?这将是调用std::stack::top()const 版本的唯一情况吗?像stack 这样的容器一直保持不变似乎毫无意义?
  • 另外,我认为即使没有 top() 的非常量版本,OP 中的代码仍然可以编译,但 std::move 将毫无意义,因为它将应用于一个const T&amp; 对象,我认为这不会导致强制转换,因此在初始化popped 时会生成一个副本,而不是移动构造函数。
  • @lamanon std::move 将源对象更改为“僵尸”对象。你不能在 const&amp; 上这样做
  • @Iamanon omg.... 你的昵称以 I not L :P 开头(这次正确地 ping 了你)
  • 有趣的是我也不记得它是 i 还是 L。另外,我知道它改变了源,但如果源是 const,它似乎仍然可以编译。例如,以下编译:T j; const T &amp;i = j; std::move(i);
猜你喜欢
  • 2013-03-21
  • 1970-01-01
  • 2019-01-02
  • 1970-01-01
  • 2011-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多