将具有复制构造函数的对象推入向量时无法避免复制
Unable to avoid copying while pushing objects with copy-construcor into a vector
我试图避免使用 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;
}
产量是'COPIED!'的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 次 'COPIED!'。
然后我尝试用 std::move
和不用 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 上执行此操作,结果相同。有没有办法摆脱所有的副本?还是我理解错了?
std::move
在这里没有用。临时 Vertex
对象已经是纯右值,因此将其转换为亡值不会改变任何内容。 class 没有移动构造函数,所以复制初始化不能移动;它必须复制。隐式移动构造函数已被用户定义的复制构造函数禁止。虽然,无论如何,移动构造函数不能比此 class 的复制构造函数快。
emplace_back
的工作方式是将参数转发给元素的构造函数。如果您传递的参数是元素类型的对象,那么您将调用接受另一个 class 实例的构造函数 - 即复制构造函数(或 classes 的移动构造函数它)。
与其创建临时 Vertex
对象,并将其作为参数传递给 emplace_back
,不如将三个 size_t
对象传递给构造函数 Vertex(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);
由于您为 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)
已经是一个右值。
but emplace_back()
.... works the same way as push_back()
in this case
是的。为了让它有所作为,您需要将构造函数参数直接传递给它,如@eerorika 的回答中所述:
vert.emplace_back(1, 2, 3);
这将通过直接在向量中构造对象来摆脱 copy/move。
通过添加默认构造函数使其成为 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} {}
};
我试图避免使用 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;
}
产量是'COPIED!'的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 次 'COPIED!'。
然后我尝试用 std::move
和不用 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 上执行此操作,结果相同。有没有办法摆脱所有的副本?还是我理解错了?
std::move
在这里没有用。临时 Vertex
对象已经是纯右值,因此将其转换为亡值不会改变任何内容。 class 没有移动构造函数,所以复制初始化不能移动;它必须复制。隐式移动构造函数已被用户定义的复制构造函数禁止。虽然,无论如何,移动构造函数不能比此 class 的复制构造函数快。
emplace_back
的工作方式是将参数转发给元素的构造函数。如果您传递的参数是元素类型的对象,那么您将调用接受另一个 class 实例的构造函数 - 即复制构造函数(或 classes 的移动构造函数它)。
与其创建临时 Vertex
对象,并将其作为参数传递给 emplace_back
,不如将三个 size_t
对象传递给构造函数 Vertex(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);
由于您为 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)
已经是一个右值。
but
emplace_back()
.... works the same way aspush_back()
in this case
是的。为了让它有所作为,您需要将构造函数参数直接传递给它,如@eerorika 的回答中所述:
vert.emplace_back(1, 2, 3);
这将通过直接在向量中构造对象来摆脱 copy/move。
通过添加默认构造函数使其成为 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} {}
};