[why is] Moving a pointer (or any other primitive object) is same as copying it.
Because in C++, you do not pay for what you do not use (in general).
A naked pointer (aka: not a smart pointer) in C++ is assumed to be non-owning with regard to the object it points to. This is why you must delete it manually; the compiler doesn't generate an implicit delete
statement for any pointers that fall out of scope.
Since a pointer does not own memory, why is it correct to null out the source of a pointer when you move one? It isn't. It's perfectly valid for a non-owning pointer's move to just copy the pointer value and leave the old one. Just like it's perfectly valid for a non-owning pointer to be destroyed without destroying what it points to.
C++ doesn't know that you need to delete that pointer, just as it doesn't know you need to null out the old one. If you need that, then you have to do it.
If you want to create an owning pointer, it is you who must create those semantics. These semantics are more expensive than non-owning semantics. A pointer move is just a bitwise-copy; a smart-pointer move has to run actual code. It has to do two memory store operations: storing the old value in the new pointer, and nulling-out the old pointer. That's more expensive than one.
And therefore, you have to explicitly ask for it or do it yourself.
To understand the potential effect on performance, consider a vector<T*>
. Now, let's consider that this type is a private member of a class. This class dynamically allocates some T
s and puts them in the vector
. It also ensures that any T
s added are deleted when the class is deleted.
So, let's consider what vector<T*>
would have to look like with your proposed idea of moving a pointer causing the old value to become nullptr
. One important element of vector
is reallocation; when you insert more elements than the vector
can hold, it has to allocate larger storage and move all of the elements to the new storage, then delete the older storage.
With your proposed idea, reallocating a vector<T*>
would mean moving all of the pointer elements. But each move is two memory operations: copying the value and nulling out the old one. But the thing is, the old value? That value is about to be destroyed. And the class that owns the vector<T*>
doesn't need to delete it or anything; it will be in the new storage. So there is no reason why vector<T*>
needed to null out a value that's going to be deleted.
By contrast, a modern vector<T*>
implementation will "move" the pointers by doing a single memcpy
of the entire buffer to the new location. It can do this because pointers are trivially-copyable, which allows them to be both copied and moved by doing byte-wise copies.
So your way is much slower. And your way gains nothing, because the old value will be dropped when the memory is deallocated. So there's no need to null it out in that case.
C++ doesn't know when you're implementing vector
or implementing a smart pointer. Therefore, it goes for the lowest-common-denominator: pointers are trivially copyable. If you want specialized move behavior, you must implement it.