I guess it is efficient, based on your definition.
Testing the different versions;
Copies: 100000005 , Construct: 1, Equal copies 0 real 0m0.075s user 0m0.073s sys 0m0.002s
and with emplace_back:
Copies: 0 , Construct: 100000005, Equal copies 0 real 0m0.195s user 0m0.191s sys 0m0.004s
you may mean it is space efficient. However, that is a choice based on use case, and seems compiler designers prefer speed.
Here is the code ( I track also equal )
struct Counter { static int count_ctor; static int count_copy; static int count_equal; Counter(int){count_ctor++;} Counter(const Counter&){count_copy++;} Counter(Counter&&) noexcept{} Counter & operator=(Counter const &){ count_equal++ ;} }; int Counter::count_copy = 0; int Counter::count_ctor = 0; int Counter::count_equal = 0; int main(void) { int size(100000005); #ifdef EMPLACE std::vector<Counter> v; v.reserve(size); for(int i = size; i>0 ; --i){ v.emplace_back(0);} #else std::vector<Counter> v(size,0); #endif std::printf("Copies: %d , Construct: %d, Equal copies %d",Counter::count_copy, Counter::count_ctor, Counter::count_equal); return 0; }
compile with g++ -DEMPLACE --std=c++11 -O3 or without EMPLACE to get the desired binary.
SECOND TEST
In order to rebute the assumptions made by the OP , the following test has been made:
- Creation of many small vectors created within multiple larger classes
- All objects created either with the default construct-copy policy or by calling an emplace wrapper function.
We produced two binaries with
g++ -DEMPLACE --std=c++11 -O3 copyc.cpp -o copyc && g++ --std=c++11 -O3 copyc.cpp -o copyc_copy
and in order to avoid any of the two having preferential treatement from the OS , we set a standard pause of 10s in between them, and we launch with the system at idle.
An exemplary run is below.
export K=10192 ; time ./copyc_copy $K ; sleep 10; time ./copyc $K Copies: 10192 , Construct: 1, Equal copies 0 real 0m2.888s user 0m0.666s sys 0m2.219s Copies: 0 , Construct: 10192, Equal copies 0 real 0m3.376s user 0m1.105s sys 0m2.270s
I've run this in multiple cases , and also in reverse
Copies: 0 , Construct: 10192, Equal copies 0 real 0m3.154s user 0m0.886s sys 0m2.267s Copies: 10192 , Construct: 1, Equal copies 0 real 0m2.573s user 0m0.531s sys 0m2.025s
That being said this is an incoclusive test, but having spent this match time on this, I bet the compiler designers did more , and all from gnu to clang and VS decided to implement a construct-copy policy. I am certain they had other reasons as well.
The code for the second test is below:
#include <vector> #include <iostream> #include <cstdlib> template<typename T> static std::vector<T> get5() { std::vector<T> s; s.reserve(5); for(int i=5; i!=0 ;--i) { s.emplace_back(T()); } return s; } struct test_struct { volatile int internals[255]; }; struct test_create { std::vector<test_struct> s; test_create() : s(5){} }; struct test_emplace { std::vector<test_struct> s; test_emplace() : s(get5<test_struct>()){} }; struct Counter { static int count_ctor; static int count_copy; static int count_equal; #ifdef EMPLACE test_emplace t[100]; #else test_create t[100]; #endif Counter(int) { count_ctor++; } Counter(const Counter&) { count_copy++; } Counter(Counter&&) noexcept { } Counter & operator=(Counter const &){ count_equal++ ;} }; int Counter::count_copy = 0; int Counter::count_ctor = 0; int Counter::count_equal = 0; int main(int arg, char const * argv[]) { int size(std::atoi(argv[1])); #ifdef EMPLACE std::vector<Counter> v; v.reserve(size); for(int i = size; i>0 ; --i) { v.emplace_back(0); } #else std::vector<Counter> v(size,0); #endif std::printf("Copies: %d , Construct: %d, Equal copies %d",Counter::count_copy, Counter::count_ctor, Counter::count_equal); return 0; }