【问题标题】:Unable to avoid copying while pushing objects with copy-construcor into a vector将具有复制构造函数的对象推入向量时无法避免复制
【发布时间】:2021-12-04 20:02:49
【问题描述】:

我试图避免使用emplace_back()reserve() 进行复制。但是当我尝试这样做时,我发现自己得到了 3 份副本,原因我无法真正理解。 reserve() 实际上有助于避免复制,但 emplace_back() 实际上对它没有任何作用(在这种情况下与 push_back() 的工作方式相同)。代码如下:

struct Vertex
{
    size_t x, y, z;

    Vertex(size_t x, size_t y, size_t z)
    {
        this->x = x;
        this->y = y;
        this->z = z;
    }

    Vertex(const Vertex& v)
        : x(v.x), y(v.y), z(v.z)
    {
        std::cout << "\nCOPIED!\n";
    }
};

int main()
{
    std::vector<Vertex> vert;

    vert.reserve(3);
    vert.emplace_back(Vertex(1, 2, 3));
    vert.emplace_back(Vertex(4, 5, 6));
    vert.emplace_back(Vertex(7, 8, 9));

    return 0;
}

输出是“复制!”的 3 倍。好吧,我尝试过这样的事情:

    vert.emplace_back(std::move(Vertex(1, 2, 3)));
    vert.emplace_back(std::move(Vertex(4, 5, 6)));
    vert.emplace_back(std::move(Vertex(7, 8, 9)));

将我的对象转换为 r 值,但我又得到了 3 次“复制!”。

然后我尝试使用std::move 推送同一个对象 3 次,然后再次得到相同的结果:

    Vertex vertex(1, 2, 3);
    vert.emplace_back(vertex);
    vert.emplace_back(vertex);
    vert.emplace_back(vertex);

    Vertex vertex(1, 2, 3);
    vert.emplace_back(std::move(vertex));
    vert.emplace_back(std::move(vertex));
    vert.emplace_back(std::move(vertex));

我不明白我做错了什么。我使用 MSVS 2022 预览版 C++14。我也尝试在 C++14/17/20 上执行此操作,结果相同。有没有办法摆脱所有的副本?还是我理解错了?

【问题讨论】:

  • 让它vert.emplace_back(1, 2, 3);emplace_back 的全部意义在于,您可以将参数传递给对象的构造函数,向量可以使用该参数在适当的位置构造对象,而不是随后必须复制的对象实例。你使用它的方式,和push_back没有什么不同
  • 你不能std::move你的对象,因为它没有移动构造函数。
  • 你使用std::move,但是因为你有用户声明的复制构造函数,你阻止了编译器为你的类创建移动构造函数的可能性。您需要声明一个移动构造函数(例如使用Vertex(Vertex&amp;&amp;) = default;)。
  • 即使有移动构造函数,也没有类成员变量的托管资源,所以没有什么可移动的。所以在这种情况下,移动与复制的作用相同。

标签: c++ vector move copy-constructor emplace


【解决方案1】:

std::move 在这里没用。临时的Vertex 对象已经是一个prvalue,所以将它转换为一个xvalue 不会改变任何东西。该类没有移动构造函数,所以复制初始化不能移动;它必须复制。隐式移动构造函数已被用户定义的复制构造函数禁止。虽然,移动构造函数无论如何都不会比这个类的复制构造函数快。

emplace_back 的工作方式是将参数转发给元素的构造函数。如果您传递的参数是元素类型的对象,那么您调用接受该类的另一个实例的构造函数 - 即复制构造函数(或具有它的类的移动构造函数)。

与其创建一个临时的Vertex 对象,并将其作为参数传递给emplace_back,不如传递三个可以转发给构造函数Vertex(size_t, size_t, size_t)size_t 对象。这样您就可以完全避免复制(和移动)Vertex 对象:

vert.emplace_back(1, 2, 3);
vert.emplace_back(4, 5, 6);
vert.emplace_back(7, 8, 9);

【讨论】:

    【解决方案2】:

    由于您为struct Vertex 提供了自定义复制构造函数,因此不会自动生成移动构造函数。如您所见,尝试移动此结构将使用复制构造函数。

    添加自定义移动构造函数:

    Vertex(Vertex &&v)
        : x(std::move(v.x)), y(std::move(v.y)), z(std::move(v.z))
    {
        std::cout << "MOVED!\n";
    }
    

    但请注意,除了打印之外,您不需要自定义复制或移动构造函数。隐式生成的就足够了。


    std::move(Vertex(1, 2, 3))
    

    std::move 在这里没有任何改变,因为Vertex(1, 2, 3) 已经是一个右值。


    emplace_back() .... 在这种情况下与push_back() 的工作方式相同

    是的。要让它有所作为,您需要将构造函数参数直接传递给它,如@eerorika 的回答中所述:

    vert.emplace_back(1, 2, 3);
    

    这将通过直接在向量中构造对象来摆脱复制/移动。

    【讨论】:

      【解决方案3】:

      通过添加默认构造函数使其成为 POD(普通旧数据):

      struct Vertex
      {
          size_t x, y, z;
      
          Vertex() = default;
      
          Vertex(size_t xx, size_t yy, size_t zz) : x{xx}, y{yy}, z{zz} {}
      };
      

      【讨论】:

        猜你喜欢
        • 2013-11-18
        • 1970-01-01
        • 1970-01-01
        • 2021-07-07
        • 2018-03-09
        • 2017-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多