我可以在 std::move 之后重用像 std::vector 这样的复杂类吗?

问题描述

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

我有一个类,每帧都将 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?我认为这是由类的赋值运算符重载决定的,但我对此感到困惑。

解决方法

您的代码始终复制 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)} {}

当通过 && 获取参数时,请记住它是一个可能移动,因为它与其他任何引用一样只是一个引用,您必须显式移动它。


这一行不会编译:

Events = std::vector();

也许你的意思是:

Events = {};

这确实允许您重复使用您的矢量。它将以导致确定状态的方式重置。

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

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

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


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

Events.reserve(60);

正如我在评论中所添加的,构造函数是移动语义的一个特例:按值获取并移动它会增加很少的成本并且很可能被省略。这就是我的意思:

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

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

由于调用移动构造函数的成本可以忽略不计,因此您无需编写重载。

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