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.
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.
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.
#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 tooList<T> lst = new();
lst.Add(obj);
Done. Sure go is nice if you only care about being hip and trendy, but C# is better in more situations than you'd think. Why bother with new languages and absurd syntax when C# has been around for decades and has perfectly clear syntax that spells out exactly what you want in simple English?
Don't be an asshole. "Just use today's trendy language instead of crusty old C++" makes you an asshole. Stop it.
Or maybe use whatever the fuck you want and let other to decide for themselves. Often people even do not have a choice.
And you know why they don't have the choice to not use C++? Because someone else made a choice to use C++ and so here we are. That's the paradox of having a freedom in chosing the language: only the first contributor has that freedom, everyone else either has to accept their decision, or leave.
Go is simpler because it is limited in functionality.