使用 copy-construcor 将对象推入向量时无法避免复制

Unable to avoid copying while pushing objects with copy-construcor into a vector

提问人:Venci 提问时间:10/17/2021 最后编辑:Venci 更新时间:10/17/2021 访问量:422

问:

我试图避免复制 和 .但是当我尝试这样做时,我发现自己得到了 3 份副本,原因我真的无法理解。 实际上有助于避免复制,但实际上对它没有任何作用(与本例的工作方式相同)。代码如下:emplace_back()reserve()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 次“复制!

然后我尝试在有和没有的情况下推送同一个对象 3 次,再次得到相同的结果:std::move

    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 上执行此操作,结果相同。有没有办法摆脱所有的副本?或者也许我对情况的理解是错误的?

C++ 矢量 移动 复制构造函数 emplace

评论

3赞 Igor Tandetnik 10/17/2021
来得及。关键是,您可以将参数传递给对象的构造函数,矢量可以使用该构造函数来就地构造对象,而不是随后必须复制的对象实例。你使用它的方式,它与vert.emplace_back(1, 2, 3);emplace_backpush_back
1赞 tkausl 10/17/2021
你不能你的对象,因为它没有移动构造函数。std::move
2赞 Yksisarvinen 10/17/2021
你使用 ,但由于你有用户声明的复制构造函数,你阻止了编译器为你的类创建移动构造函数的可能性。您需要声明一个移动构造函数(例如 with )。std::moveVertex(Vertex&&) = default;
3赞 Eljay 10/17/2021
即使有 move-constructor,类成员变量也没有托管资源,因此没有什么可移动的。因此,在这种情况下,移动与复制执行相同的操作。

答:

2赞 eerorika 10/17/2021 #1

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

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

与其创建一个临时对象,并将其作为参数传递给 ,不如将三个可以转发给构造函数的对象。这样,您可以完全避免复制(和移动)对象:Vertexemplace_backsize_tVertex(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);
1赞 HolyBlackCat 10/17/2021 #2

由于您提供了 的自定义复制构造函数,因此不会自动生成移动构造函数。如您所见,尝试移动此结构将使用 copy 构造函数。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);

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

0赞 grigouille 10/17/2021 #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} {}
};