std::move会使指针失效吗?

12

假设以下情况:

template<typename Item>
class Pipeline
{
    [...]

    void connect(OutputSide<Item> first, InputSide<Item> second)
    {
        Queue<Item> queue;
        first.setOutputQueue(&queue);
        second.setInputQueue(&queue);
        queues.push_back(std::move(queue));
    }

    [...]

    std::vector<Queue<Item> > queues;
};

移动后,指向队列的指针在“first”和“second”中是否仍然有效?

3个回答

13

std::move会使指针失效吗?

不会。移动对象后,该对象仍然存在,因此指向该对象的任何指针仍然有效。如果Queue被合理实现,那么从中移动后应该将其保留在有效状态(即可以安全地销毁或重新分配它);但可能会更改其状态(例如,将其置为空)。

移动后“first”和“second”中的指针是否仍然有效?

不会。它们将指向已从中移动的本地对象;如上所述,您不能对移动后的对象状态做出任何假设。

比这更糟糕的是,当函数返回时,它被销毁,留下悬空指针。它们现在无效,不指向任何对象,并且使用它们会导致未定义的行为。

也许您想要它们指向已移入到queues中的对象:

queues.push_back(std::move(queue));
first.setOutputQueue(&queue.back());
second.setInputQueue(&queue.back());

但是,由于queues是一个向量,当队列重新分配其内存时,这些指针将无效。

为解决这个问题,可以使用像dequelist这样的容器,它们在插入后不会移动其元素。或者,以额外的间接层为代价,可以存储(智能)指针而不是对象,如Danvil的答案中所述。


我不明白为什么这个答案得到了(-1)。提供的解释还可以,而且解决方法(先推入,然后获取指针,而不是反过来)是有效的,假设我们读到最后并将“queues”更改为列表类型容器。 - quetzalcoatl
如果那位点踩的人能解释一下哪里出了问题就好了。我想纠正我在这里犯的任何错误,但是一个没有解释的点踩并不能帮助我做到这一点。 - Mike Seymour
@MikeSeymour:我希望至少第一个点踩的人能够评论一下他的理由 :( - Matthieu M.
我也想选择这个答案,因为它非常有帮助。不幸的是,我只能选择一个答案,而我的声望还不足以让我点赞你的回答。你确实为我解决了问题,谢谢。 - Jan Stephan

7
指针无法使用,因为“队列”是一个本地对象,在“connect”的末尾将被删除。即使使用“std::move”,您仍会在新的内存位置上创建一个新对象。它只会尽可能多地使用“旧”对象。
此外,整个过程根本不起作用,无论是否使用“std::move”,因为“push_back”可能需要重新分配。因此,对“connect”的调用可能会使所有旧指针无效。
一种可能的解决方案是在堆上创建“Queue”对象。以下建议使用C++11:
#include <memory>

template<typename Item>
class Pipeline
{
    [...]

    void connect(OutputSide<Item> first, InputSide<Item> second)
    {
        auto queue = std::make_shared<Queue<Item>>();
        first.setOutputQueue(queue);
        second.setInputQueue(queue);
        queues.push_back(queue);
    }

    [...]

    std::vector<std::shared_ptr<Queue<Item>>> queues;
};

也许可以看看Seymour的回答。在这里管理共享指针没有其他理由(除了坚持使用向量!)。只需先将其推入列表,然后使用新添加的对象即可。(编辑) 当然,总是可以进入列表与向量的讨论,但是,还有其他容器可供选择。 - quetzalcoatl
@quetzalcoatl:这只是使用shared_ptr的建议。个人而言,我更喜欢它,而不是对给定容器的实现方式进行假设。 - Danvil
在这里,我觉得没有理由使用shared_ptr而不是unique_ptr;因此,我建议使用unique_ptr - Matthieu M.

2
其他人提供了不错且详细的解释,但是你的问题表明你并没有完全理解move的作用以及设计原理。我会尝试用简单的语言来描述它。 move,如其名,是用于移动东西的。但是什么可以被移动呢?一旦已经分配好了一个对象,就不能再移动它了。回想一下最近添加的新移动构造函数,它类似于复制构造函数。
所以,这都与对象的“内容”有关。复制构造函数和移动构造函数都是用于操作对象的“内容”。std::move也同样如此。它的目的是将一个对象的内容移动到目标位置,相比于copy,它的目的是在原始位置不留下任何内容痕迹。
它应该被用于任何需要强制我们进行副本但我们并不真正关心它的情况,并且只有在目标位置中已经存在内容时我们才需要它。
也就是说,“move”的使用表明会创建副本将内容移动到其中,并且清除原始内容(有时可能会跳过某些步骤,但基本思想就是这样)。
这明确指出,即使原始内容幸存下来,任何指向原始对象的指针也不会指向接收到内容的目标位置。最好的情况是,它们将指向刚刚 “清除” 的原始东西。最糟糕的情况是,它将指向一个完全无法使用的东西。
现在看看你的代码。它填充了queue,然后获取指向原始对象的指针,然后moves queue
我希望现在你清楚地知道正在发生什么以及你可以如何处理它了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接