3 ms·
basically: #include <format> #include <string> #include <vector> struct my_type { std::string data; my_type(int value): d
by jcelerier 2y ago
basically:
#include <format>
#include <string>
#include <vector>
struct my_type {
std::string data;
my_type(int value): data(value, 'A')
{
puts("int ctor\n");
}
my_type() { puts("default ctor\n"); }
my_type(const my_type&) { puts("copy ctor\n"); }
my_type(my_type&&) noexcept { puts("move ctor\n"); }
my_type& operator=(const my_type&) { puts("copy assign\n"); return *this; }
my_type& operator=(my_type&&) noexcept { puts("move assign\n"); return *this; }
};
int main()
{
puts("Case A\n");
{
std::vector<my_type> vec;
vec.emplace_back(123);
}
puts("Case B\n");
{
std::vector<my_type> vec;
vec.push_back(my_type{123});
}
}
Here, in the first case (in a very schematic way), the vector:
1/ allocates the memory for an element in for instance:
my_type* mem_begin = std::allocator<my_type>::allocate(...);
2/ calls
std::construct_at(&mem_begin[0], 123);
which directly creates the object and calls my_type::my_type(int) constructor in the std::vector's memory storage. The only output you'll see will be
int ctor
The inner std::string will also be initialized directly in the right memory position, at no point there will be a copy of, say, 10000 'A' characters.
In the second case, first you construct my_type on the stack of the calling code so you get a first call to
my_type::my_type(int)
Then my_type is moved (or copied, if it didn't have move constructors): std::vector's implementation does pretty much the same thing, but the result is two constructions instead of one:
my_data* mem_begin = std::allocator<my_data>::allocate(...);
...
std::construct_at(&mem_begin[0], instance_of_my_data_passed_in_argument);
which ends up calling my_type::my_type(my_type&&) ; you'll see
int ctor
move ctor
and the inner string will be copied / moved too