【问题标题】:Do I use the move semantic correctly? What would be the benefit?我是否正确使用了移动语义?会有什么好处?
【发布时间】:2023-04-03 06:25:01
【问题描述】:

我想知道我是否正确使用了move 语义:

class Vertex{
    protected:
        Common::Point3D position;
        Common::Point3D normal;
        Common::Point2D uv;
        Common::Point2D tangent;
    public:
        Vertex(Common::Point3D &&position, Common::Point3D &&normal, Common::Point2D &&uv, Common::Point2D &&tangent)
            : position(std::move(position)), normal(std::move(normal)), uv(std::move(uv)), tangent(std::move(tangent)){}
};

我将在这里用move 实现什么? 比较代码(我也可以使用const &):

class Vertex{
    protected:
        Common::Point3D position;
        Common::Point3D normal;
        Common::Point2D uv;
        Common::Point2D tangent;
    public:
        Vertex(Common::Point3D position, Common::Point3D normal, Common::Point2D uv, Common::Point2D tangent)
            : position(position), normal(normal), uv(uv), tangent(tangent){}
};

将阻止多少次复制,其中哪些会发生?

我想使用这样的代码:

Vertex * vertices = new Vertex[10000];
vertices[0] = Vertex(Common::Point3D(1,2,3), Common::Point3D(4,5,6)...);
vertices[1] = ...

我可以进一步优化它吗?

【问题讨论】:

  • 有没有好处就看Point2DPoint3D的内容了,从他们的名字来看,搬家很可能没有好处。
  • Point3D 只是 class Point3D{float x, y,z;} 与一些自定义 setget 方法。
  • 然后复制这些内容或在适当的地方通过引用传递。这里没有什么可以加速的。
  • @PolGraphic 所以不。搬家对复制没有好处。但是这两个代码 sn-ps 之间的区别在于,第一个构造函数只接受右值,而第二个构造函数同时接受左值和右值。
  • 这就是我的想法。对于基本类型,移动和复制是相同的,所以这个类根本不会从移动中受益。为了约定,我可能会将构造函数定义为 Vertex(Common::Point3D position, ...) : position(std::move(position)), ... {} 与您的第一个定义不同,这个也将接受左值(如果没关系,那么您的第一个定义就可以了)

标签: c++ c++11 move move-semantics


【解决方案1】:

我想知道我是否正确使用了移动语义:

通过仅接受右值引用,您限制了将左值传递给此构造函数。你可能很快就会发现你不能像你期望的那样使用它。如果您按值接受类型...

Vertex(Common::Point3D position, Common::Point3D normal, Common::Point2D uv, Common::Point2D tangent)
    : position{std::move(position)}
    , normal{std::move(normal)}
    , uv{std::move(uv)}
    , tangent{std::move(tangent)}
{}

...您允许该类的用户将movecopy 放入构造函数变量中,然后您始终将move 放入您的成员变量中。这意味着您将始终导致 1 次复制和 1 次移动或 2 次移动,具体取决于调用 ctor 的方式。或者,您可以通过const& 获取,并且无论如何总是准确地生成 1 个副本。如果您想成为无可否认的最快速度,那么&&const& 的每个组合都会重载,但代码太多了。

我将在这里通过 move 实现什么目标?

假设 Point3D 仅包含 3 个整数/浮点数/双精度数,您将一无所获。基本类型不会从move 得到任何东西,它和副本一样。

移动的最大好处通常是您可以“窃取”动态分配的内存。想想vector,它动态分配一个数组并持有一个指向它的指针。当您move 它时,它可以简单地将该指针复制到新对象并将原始指针清空。当您复制它时,它会在新对象中分配新内存并将数组(可能逐个元素)复制到新数组中。巨大的成本差异:分配和复制数组元素与仅复制指针相比。

结论:对于您的类型,通过const& 可能是最快的方法。

【讨论】:

  • 感谢您提供非常详细的回答。动态分配的数组 (Vertex * vertices) 会从 move 中受益吗(就像 std::vector 那样)?
最近更新 更多