1

In the classes dealing with dynamic mem allocation, shallow copy basically causes the program to delete a resource twice. In move operations, the original pointer no longer points to the resource, So But why the same behavior occurs in move semantics? e.g:

#include <utility>
#include <cstring>
using namespace std;
class MyString
{
    char* cstr;
 public:
    MyString(const char* arg)
    : cstr(new char[strlen(arg)+1])
    {
        strcpy(cstr, arg);
    }
    MyString(MyString&&) = default;
    MyString& operator=(MyString&&) = default;
    MyString(const MyString&) = delete;
    MyString& operator=(const MyString&) = delete;
    ~MyString()
    {
        delete[] cstr;
    }
};

int main()
{
    MyString S1{"aaa"};
    MyString S2 = move(S1); // error

}

I've tried with 3 different compilers and i got the same results.

  • 4
    You still have to null out the original pointer which the default move constructor doesn't do for you. – Captain Obvlious Jul 29 '17 at 01:05
  • 1
    "the original pointer no longer points to the resource" - why? With raw pointers it is your responsibility to ensure that. And you are not ensuring it. – AnT stands with Russia Jul 29 '17 at 01:11
  • 3
    This is why committee members argued that `move` was misleading. It's a type-cast. It doesn't actually do anything. – AndyG Jul 29 '17 at 01:13
  • If one of the compilers you used is Visual Studio, the debugger clearly shows what the issue is, i.e., the pointer was not "moved". Also [read the first paragraph in the definition of std::move](http://en.cppreference.com/w/cpp/utility/move). Note the word *indicate*. – PaulMcKenzie Jul 29 '17 at 01:15
  • @AndyG std::move causes the move ctor to be called witch then makes memberwise move. I didn't get your point that "it doesn't actually do anything" it's causing the move ctor to be executed. – Iman A. Fazel Jul 29 '17 at 01:19
  • @CaptainObvlious If it's not causing the original pointer to be null, it's clearly not holding its original value either. That alone should prevent the double-destruction of a same memory. – Iman A. Fazel Jul 29 '17 at 01:23
  • 1
    Yes, it is holding its original value. What makes you think it's not? – Benjamin Lindley Jul 29 '17 at 01:27

2 Answers2

5

The implicitly generated move constructor move-constructs each member. Your member is a pointer. Moving a pointer (or any other primitive object) is same as copying it.

So, since your move constructor does nothing else besides moves the pointer, the moved from MyString object will still be pointing to the same pointee object as the moved to MyString object. And when both are destroyed, the destructor will try to delete the pointee twice.

You need to follow the rule of 5: If any of destructor, move/copy constructor, move/copy assignment needs to be implemented, then all of them probably need to be implemented (or deleted). You've implemented the destructor to delete an owned pointer, so you must implement the move constructor (and the others) such that the moved from (or copied from) object no longer points to the object that it no longer owns.

Why move semantics have the same behavior as shallow copy in dynamic mem allocation?

Because moving is just another word for shallow copying. The move constructor and assignment operator are there to be given a custom implementation in case the moved from object needs to be cleaned up to maintain the class invariant. Just like the copy constructor and copy assignment operator are there to make a deep copy that does not break the class invariant.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thank you for the answer but why "Moving a pointer (or any other primitive object) is same as copying it.",? I don't understand, then what's whole the point of std::move on lvalues? isn't it to "move" the values? – Iman A. Fazel Jul 29 '17 at 01:43
  • The point of std::move is to convert an lvalue to an rvalue. The point of the conversion is to allow copy initialization of another object by move. – eerorika Jul 29 '17 at 01:49
  • and the move doesn't transfer/move the values? – Iman A. Fazel Jul 29 '17 at 02:01
  • @joghd This question might help: [What are move semantics?](https://stackoverflow.com/questions/3106110/what-are-move-semantics) – Blastfurnace Jul 29 '17 at 02:33
  • @Blastfurnace Thanks for the link. I already checked that out. The point that i still can't understand is that it seems `std::move` sometimes transfers its argument's value, sometimes it doesn't. The first case is what happened in my example. My conclusion is that, the values of `move`ed object may or may not be transfered; but if they don't, they are not stable/guaranteed. Have i understood it right? – Iman A. Fazel Jul 29 '17 at 02:44
  • @joghd: `std::move` does not provoke the actual movement of the object. What does the actual moving is the construction of the new object or assignment to an existing object from the *return value* of `std::move`. Furthermore, exactly what "move" means depends on the object(s) in question. An object which does not own resources will likely have "move" be no different from a "copy". – Nicol Bolas Jul 29 '17 at 02:50
  • @NicolBolas Thank you very much. That was exactly what i was looking for. – Iman A. Fazel Jul 29 '17 at 03:00
3

[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 Ts and puts them in the vector. It also ensures that any Ts 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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982