为什么std :: vector在初始化时强制复制?
问题描述:
我有一个复制/移动探测类:为什么std :: vector在初始化时强制复制?
#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
s可能是值得的。
另一种选择:
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一样高效。
这是因为正在使用'std :: initializer_list'构造函数。这种类型的语义是不幸的。 – StoryTeller
本问答解释了这种情况,并提供了一些解决方法:https://stackoverflow.com/questions/8468774/can-i-list-initialize-a-vector-of-move-only-type –
@StoryTeller @MM I看,所以问题不是'std :: vector',而是'std :: initializer_list',显然不能转发右值引用?如果有人可以请发表正确的答案,我会接受。 – jdehesa