Comment by criemen
2 months ago
Would it be fair to say that things are so complicated (compared to all other programming languages I've used in my professional life), because C++ pre-move semantics defaulted to deep copy semantics? It seems to be set apart in that choice from many other languages.
Deep copy is pedagogically and semantically the right choice for any mutable containers. You either make containers immutable or copies deep. Otherwise it's just an invitation for subtle bugs.
I'm not sure about that - every time I copy an object I have to think through what happens, no matter the default semantics. C++ makes the deep copy case easier than other programming languages without top-level built-in support.
No, it should move properly when passing by value (as in, essentially the rust move semantics). If you want a copy, that should be explicit.
Moving by default would be too much of a footgun without a borrow checker imo.
2 replies →
Then explain that const isn’t deep and a const container can end up mutating state? Pretending like c++ has a consistent philosophy is amusing and pretending this happened because of pedagogy is amusing. It happened because in c assignment is a copy and c++ inherited this regardless of how dumb it is as a default for containers.
In C++ regular types have the property that const is deep. If you have a const std::vector<int> the you can't modify any of the integers contained in this container. Naturally for flexibility reasons not all types are regular, pointers being the prominent exception, and things like std::string_view being modern examples of non-regular types.
4 replies →
It certainly makes things easier. But it also makes some things very, very, very inefficient. I want a list with millions/billions of elements. I want to regularly change one of the elements somewhere in the middle. Good luck with the copying.
Why would you copy a whole list to modify a single element?
1 reply →
Correct. As someone who maintain a 16-year-old C++ code base with new features added every day, The status quo is the best incremental improvement over deep copy semantics.
There are better choices if everything is built from scratch, but changing wheels from a running car isn't easy.
Sorry to be the nitpicker here, but - the best incremental improvement would probably have seen move-destruction instead of just moves, which keep the source object alive and force the allowance for a valid 'empty' or 'dummy' state.
See also:
* https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n13... move designs
* https://www.foonathan.net/2017/09/destructive-move/
C++ supports both pass-by-value and pass-by-reference parameters. Pass-by-value means making a copy (a deep copy if it's a deep type), but you could always choose to optimize by passing large parameters by reference instead, and this is common practice.
The real value of std::move is cases where you to HAVE to (effectively) make a deep copy, but still want to avoid the inefficiency. std::move supports this because moving means "stealing" the value from one variable and giving it to another. A common use case is move constructors for objects where you need to initialize the object's member variables, and can just move values passed by the caller rather than copying them.
Another important use case for std::move is returning values from functions, where the compiler will automatically use move rather than copy if available, allowing you to define functions returning large/complex return types without having to worry about the efficiency.
1990's C practice was to document whether initialization values were copied or adopted. I'm curious why the concept became "move" rather than "adopt", since move gives the parameter/data agency instead of giving agency to the consuming component.
I'm not sure how any notion of ownership and adoption in C would carry over to C++, since one of the problems with C is that it didn't define ownership. If you have a pointer value in C it means nothing about ownership.
Given the limits of C, "adopting" a pointer variable would just mean that the foster-parent now had a copy of the pointer, and was considered owner of it, now responsible for freeing it too presumably. It's basically a move, except the source can now mess things up by still using the adopted value after the adoption.
(More so since c++17) std::move should not be used for returns because this pessimises optimisations.
> defaulted to deep copy semantics
It defaulted to pass-by-value, with shallow copy semantics, as opposed to pass by reference.
No, pass-by-value means copying, which means whatever the copy constructor of the type implements, which for all standard types means a deep copy.
You COULD define you own type where the copy constructor did something other than deep copy (i.e. something other than copy!), just as you could choose to take a hand gun and shoot yourself in the foot.
https://en.cppreference.com/w/cpp/language/copy_constructor....
> For non-union class types, the constructor performs full member-wise copy of the object's direct base subobjects and member subobjects, in their initialization order, using direct initialization. For each non-static data member of a reference type, the copy constructor binds the reference to the same object or function to which the source reference is bound.
Would you call this deep or shallow? I think most would call it a shallow copy.
1 reply →
"Deep copy" is quite hand-wavy. A useful program is not a collection of trees that you can just copy like that. Instead, typical program's data structures tend to have lots of references that let you go from anywhere to just about anywhere else. That's why "deep copy" is just a rough idea that may work for simple container types but not much else.
6 replies →
You seem to misunderstand the meaning of deep versus shallow copy. This distinction has to do with how references/pointers get copied.
In C++ the compiler generated copy constructors are shallow, not deep, meaning that if you want a copy to reconstruct the object being pointed to, you can not use the default copy constructor but need to supply your own explicit copy constructor to do so.
5 replies →
I consider it a "shallow" copy because it doesn't copy pointer or reference members.
1 reply →