I have a copy/move probing 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;
}
};
And I have found that running:
#include <vector>
int main(int, char **)
{
std::vector<A> v { A() };
}
Produces the following output:
Creating A
Copying A
Deleting A
Deleting A
Why won't the initialization just move the objects? I know that std::vector
may create undesired copies on resize, but as you can see, adding noexcept
did not help here (and besides, I don't think that the reasons a resize causes copies apply to initialization).
If I instead do the following:
std::vector<A> v;
v.push_back(A());
I don't get copies.
Tested with GCC 5.4 and Clang 3.8.
This isn't std::vector
, but std::initializer_list
.
std::initializer_list
is backed by a const
array of elements. It does not permit non-const
access to its data.
This blocks moving from its data.
But this is C++, so we can solve this:
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;
}
now we get:
auto v = make_vector<A>( A() );
gives you 1 extra move per element:
Creating A
Moving A
Moving A
Deleting A
Deleting A
Deleting A
We can eliminate that extra instance with a careful bit of reserving and emplacing back:
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 -- simply swap v2::
for v1::
to see the first one in action.
Output:
Creating A
Moving A
Deleting A
Deleting A
there could be a bit more vector overhead here, as it may be hard for the compiler to prove that emplace_back
does not cause reallocation (even though we can prove it), so redundant checks will be compiled in most likely. (In my opinion, we need an emplace_back_unsafe
that is UB if there isn't enough capacity).
The loss of the extra set of A
s is probably worth it.
Another choice:
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;
}
which is used like
auto v = make_vector<1,A>({{ A() }});
where you have to specify how many elements manually. It is as efficient as the version 2 above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With