3 ms·
I’m not interested in who you’re replying to, but your question seems to imply there’s some way to add data to a container without already having some copy. Tha
by leecommamichael 2y ago
I’m not interested in who you’re replying to, but your question seems to imply there’s some way to add data to a container without already having some copy. That’s only possible if the container (and it’s data) is static.
The discrimination is the penalty of copying a stack-value vs some GC’d/managed memory. Then if you compare those scenarios the semantics lead to more interesting topics for debate.
- pavlov 2y ago> “some way to add data to a container without already having some copy” That’s what C++ vector emplace_back does. It allocates the memory if needed, then constructs the object in place using the provided arguments. No need for a copy.
- leecommamichael 2y agoI'm interested in the semantics of "if needed" and "constructs in place." I'm confused as to how you could initialize dynamic memory without a copy. I feel like initializing dynamic memory _is_ copying (memcpy), unless we're talking about some sort of fully constexpr thing.
- jcelerier 2y agobasically: #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