【问题标题】:Can I re-use a complex class like std::vector after std::move?我可以在 std::move 之后重新使用像 std::vector 这样的复杂类吗?
【发布时间】:2021-05-05 18:50:37
【问题描述】:

我正在创建一个游戏,并尝试学习移动语义/r 值引用。

我有一个类,每帧将Event 添加到vector 中。

60 帧后,我想将所有累积的事件移动到一个新类中(称为Step),该类将存储在另一个vector 中。

我想重复这个过程,所以在移动Event 向量后,它应该重置为空。

#include <vector>

class Event
{
    ...
}

class Step
{
    std::vector<Event> Events;

    Step(std::vector<Event>&& InEvents) : Events {InEvents} {}
}

class EventAccumulator
{
    std::vector<Event> Events;
    std::vector<Step> Steps;

    void Update(int FrameCount)
    {
        Events.push_back(Event());

        if (FrameCount % 60 == 0)
        {
            // Move accumulated events into a new Step.
            Step NewStep = Step(std::move(Events));
            Steps.push_back(NewStep);

            // Reset Events so that we can accumulate future events.
            Events = std::vector<Event>();
        }
    }
}

// Game Loop
int GlobalFrameCount = 0;
EventAccumulator eventAccumulator{};
while (true)
{
    eventAccumulator.Update(GlobalFrameCount++);
}

据我了解,Step NewStep = Step(std::move(Events)); 行将“给予”EventsNewStep(即不复制向量)。如果我在这方面错了,请纠正我。

我希望Events = std::vector(); 行导致EventAccumulator.Events 被重置为空向量,但我不希望NewStep.Events 被重置。

我的问题是,这段代码会做我想要的吗?

此外,您如何判断这是否适用于所有复杂的类,例如std::vector?我认为这是由类的赋值运算符重载决定的,但我对此感到困惑。

【问题讨论】:

  • 你问题后半部分的答案可以在这里找到:stackoverflow.com/questions/9168823/reusing-a-moved-container
  • @NathanOliver 不仅如此。 OP 担心通过重新分配Events 也可能清除新构建的向量。
  • 如果你给Step一个默认构造函数,一个新构造的Step对象将有一个空的Events向量。然后你可以使用std::swap(NewStep,Events, Events)来传输Events into the NewStep`对象的向量,并清空旧的Events对象。可能有更好的方法可以做到这一点,但std::swap 是这里的关键。不需要移动构造函数等。

标签: c++ visual-c++ c++17 move-semantics rvalue-reference


【解决方案1】:

您的代码始终复制Events 向量,因此Events = std::vector(); 将简单地删除复制的元素。

为什么它是复制而不是移动?我们来看看Step的构造函数:

//                         that's a copy -----v
Step(std::vector<Event>&& InEvents) : Events {InEvents} {}

确实,表达式(InEvents) 是一个左值,因为它有一个名称。您必须使用 std::move 将其转换为右值:

Step(std::vector<Event>&& InEvents) : Events {std::move(InEvents)} {}

当通过&amp;&amp; 获取参数时,请记住它是一个可能移动,因为它只是一个引用,就像任何其他引用一样,您必须明确地移动它。


此行无法编译:

Events = std::vector();

也许你的意思是这样的:

Events = {};

这确实可以让您重用您的矢量。它将以导致确定状态的方式重置。

我希望Events = std::vector(); 行导致EventAccumulator.Events 被重置为空向量,但我不希望NewStep.Events 被重置。

我的问题是,这段代码会做我想要的吗?

C++ 具有值语义。除非NewStep 包含std::vector&lt;Event&gt;&amp;,否则您不能影响其他地方的变量。您还可以在NewStep 中移动构造向量。这应该告诉你一些事情:你构建了一个新的向量。无论您如何改变旧向量,它都不会影响不同的向量。如果您更正 Step 构造函数,此代码将执行您想要的操作。


请记住,如果您想避免多次分配,您必须像这样调用reserve

Events.reserve(60);

正如我在 cmets 中添加的那样,构造函数是移动语义的一种特殊情况:按值获取并移动它会增加很少的成本,而且很可能会被省略。这就是我的意思:

Step(std::vector<Event> InEvents) : Events {std::move(InEvents)} {}

如果你通过副本,那么它将复制到InEvents并移动它。如果你通过move传递,它会调用move构造函数两次,没有复制。

由于调用移动构造函数的成本可以忽略不计,它使您免于编写重载。

它只适用于构造函数,因为我们无法重用容量。这不适用于赋值或 setter 函数。

【讨论】:

  • 请注意,Events.clear() 将保留向量容量(即不会丢失分配),而 Events = {} 不会。
  • @peterchen 哦,真的吗?我有一些代码要更改。
  • @peterchen 你能在从向量移动的时候调用这个函数吗?
  • 是的,但是。移出对象处于未指定但有效的状态。它可能是空的,或者有之前的内容,或者介于两者之间。 clear() 保证 size()==0(并且,间接地,capacity() 不会改变。)但是你不知道移动后的容量。适合您情况的理想组合是Events.clear(); Events.reserve(...)。如果移动保留了分配,reserve 将是空操作。
  • 感谢您的回答。我想我明白现在发生了什么。我认为std::move 会将Events 的所有权授予Step,而无需进行任何复制。但是现在我看到必须复制堆栈变量,但只有指针/引用成员变量可以移动,而无需复制它们指向/引用的数据。移动的成员由类的 (vector's) 移动构造函数决定,我们使用 std::move 调用它(如您所描述的)。如果我错了,请纠正我
猜你喜欢
  • 1970-01-01
  • 2019-04-23
  • 1970-01-01
  • 2015-06-04
  • 2017-10-19
  • 1970-01-01
  • 2019-07-09
  • 2017-05-18
相关资源
最近更新 更多