为什么 std::vector 在初始化时强制复制?
Why does std::vector enforce copy on initialization?
我有一个 copy/move 探测 class:
#include <iostream>
struct A
{
A()
{
std::cout << "Creating A" << std::endl;
}
~A() noexcept
{
std::cout << "Deleting A" << std::endl;
}
A(const A &)
{
std::cout << "Copying A" << std::endl;
}
A(A &&) noexcept
{
std::cout << "Moving A" << std::endl;
}
A &operator=(const A &)
{
std::cout << "Copy-assigning A" << std::endl;
return *this;
}
A &operator=(A &&) noexcept
{
std::cout << "Move-assigning A" << std::endl;
return *this;
}
};
而且我发现 运行:
#include <vector>
int main(int, char **)
{
std::vector<A> v { A() };
}
产生以下输出:
Creating A
Copying A
Deleting A
Deleting A
为什么初始化不只是移动对象?我知道 std::vector
可能会创建 undesired copies on resize,但如您所见,添加 noexcept
在这里没有帮助(此外,我认为调整大小导致副本的原因不适用于初始化)。
如果我改为执行以下操作:
std::vector<A> v;
v.push_back(A());
我没有得到副本。
已使用 GCC 5.4 和 Clang 3.8 进行测试。
这不是 std::vector
,而是 std::initializer_list
。
std::initializer_list
由 const
元素数组支持。它不允许非 const
访问其数据。
这会阻止从其数据中移动。
但这是C++,所以我们可以解决这个问题:
template<class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(Args&&...args) {
std::array<T, sizeof...(Args)> tmp = {{std::forward<Args>(args)...}};
std::vector<T,A> v{ std::make_move_iterator(tmp.begin()), std::make_move_iterator(tmp.end()) };
return v;
}
现在我们得到:
auto v = make_vector<A>( A() );
每个元素给你 1 个额外的移动:
Creating A
Moving A
Moving A
Deleting A
Deleting A
Deleting A
我们可以通过谨慎的保留和安置来消除那个额外的实例:
template<class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(Args&&...args) {
std::vector<T,A> v;
v.reserve(sizeof...(args));
using discard=int[];
(void)discard{0,(void(
v.emplace_back( std::forward<Args>(args) )
),0)...};
return v;
}
Live example of both -- 只需将 v2::
换成 v1::
即可查看第一个的效果。
输出:
Creating A
Moving A
Deleting A
Deleting A
这里可能会有更多的向量开销,因为编译器可能很难证明 emplace_back
不会导致重新分配(即使我们可以证明),因此将编译冗余检查在最有可能。 (在我看来,如果没有足够的容量,我们需要一个 emplace_back_unsafe
即 UB)。
失去额外的 A
组可能是值得的。
另一个选择:
template<std::size_t N, class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(std::array<T, N> elements) {
std::vector<T,A> v{ std::make_move_iterator(elements.begin()), std::make_move_iterator(elements.end()) };
return v;
}
像
一样使用
auto v = make_vector<1,A>({{ A() }});
您必须在其中手动指定元素的数量。它与上面的版本 2 一样高效。
我有一个 copy/move 探测 class:
#include <iostream>
struct A
{
A()
{
std::cout << "Creating A" << std::endl;
}
~A() noexcept
{
std::cout << "Deleting A" << std::endl;
}
A(const A &)
{
std::cout << "Copying A" << std::endl;
}
A(A &&) noexcept
{
std::cout << "Moving A" << std::endl;
}
A &operator=(const A &)
{
std::cout << "Copy-assigning A" << std::endl;
return *this;
}
A &operator=(A &&) noexcept
{
std::cout << "Move-assigning A" << std::endl;
return *this;
}
};
而且我发现 运行:
#include <vector>
int main(int, char **)
{
std::vector<A> v { A() };
}
产生以下输出:
Creating A
Copying A
Deleting A
Deleting A
为什么初始化不只是移动对象?我知道 std::vector
可能会创建 undesired copies on resize,但如您所见,添加 noexcept
在这里没有帮助(此外,我认为调整大小导致副本的原因不适用于初始化)。
如果我改为执行以下操作:
std::vector<A> v;
v.push_back(A());
我没有得到副本。
已使用 GCC 5.4 和 Clang 3.8 进行测试。
这不是 std::vector
,而是 std::initializer_list
。
std::initializer_list
由 const
元素数组支持。它不允许非 const
访问其数据。
这会阻止从其数据中移动。
但这是C++,所以我们可以解决这个问题:
template<class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(Args&&...args) {
std::array<T, sizeof...(Args)> tmp = {{std::forward<Args>(args)...}};
std::vector<T,A> v{ std::make_move_iterator(tmp.begin()), std::make_move_iterator(tmp.end()) };
return v;
}
现在我们得到:
auto v = make_vector<A>( A() );
每个元素给你 1 个额外的移动:
Creating A
Moving A
Moving A
Deleting A
Deleting A
Deleting A
我们可以通过谨慎的保留和安置来消除那个额外的实例:
template<class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(Args&&...args) {
std::vector<T,A> v;
v.reserve(sizeof...(args));
using discard=int[];
(void)discard{0,(void(
v.emplace_back( std::forward<Args>(args) )
),0)...};
return v;
}
Live example of both -- 只需将 v2::
换成 v1::
即可查看第一个的效果。
输出:
Creating A
Moving A
Deleting A
Deleting A
这里可能会有更多的向量开销,因为编译器可能很难证明 emplace_back
不会导致重新分配(即使我们可以证明),因此将编译冗余检查在最有可能。 (在我看来,如果没有足够的容量,我们需要一个 emplace_back_unsafe
即 UB)。
失去额外的 A
组可能是值得的。
另一个选择:
template<std::size_t N, class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(std::array<T, N> elements) {
std::vector<T,A> v{ std::make_move_iterator(elements.begin()), std::make_move_iterator(elements.end()) };
return v;
}
像
一样使用auto v = make_vector<1,A>({{ A() }});
您必须在其中手动指定元素的数量。它与上面的版本 2 一样高效。