Jump to content

  • Log In with Google      Sign In   
  • Create Account

C++ std::move() vs std::memcpy()


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
14 replies to this topic

#1 Ryan_001   Prime Members   -  Reputation: 1409

Like
0Likes
Like

Posted 20 April 2014 - 11:02 AM

I was working on a vector-like container, and while writing a resize() function came across an interesting question.

 

When moving an object between locations, when would std::move() not give the same results as simply a std::memcpy()?

 

There were only two situations that I could think of.  One was when the move constructor (or assignment operator) was intentionally used to do something strange (ie. not actually move).  The other that when using std::move() the src object could (in theory) be reused, or at the very least has to be destroyed, where-as with the std::memcpy() neither applies.

 

But in every other case I could think of (pointers, handles, reference counting) simply copying the bits should have the same result as calling std::move().?



Sponsor:

#2 BitMaster   Crossbones+   -  Reputation: 4136

Like
4Likes
Like

Posted 20 April 2014 - 11:07 AM

You might want to read the documentation of std::is_trivially_copyable.



#3 Ryan_001   Prime Members   -  Reputation: 1409

Like
0Likes
Like

Posted 20 April 2014 - 11:19 AM

You might want to read the documentation of std::is_trivially_copyable.

 

I know the difference between POD and non-POD types.  Clearly when copying you can't just memcpy(), things like pointers, handles, COM interfaces, reference counting, ect... need to be updated.  

 

Closer to the topic is: http://en.cppreference.com/w/cpp/types/is_move_constructible.  Which has std::is_trivially_move_constructable.  Apart from throwing exceptions (which move is not supposed to do, much like destructors) what does calling std::move give you that std::memcpy() doesn't?



#4 BitMaster   Crossbones+   -  Reputation: 4136

Like
1Likes
Like

Posted 20 April 2014 - 12:26 PM

Actually, my link does list all the important information. As the notes say:

Objects of trivially-copyable types are the only C++ objects that may be safely copied with std::memcpy or serialized to/from binary files with std::ofstream::write()/std::ifstream::read(). In general, a trivially copyable type is any type for which the underlying bytes can be copied to an array of char or unsigned char and into a new object of the same type, and the resulting object would have the same value as the original.

 

The properties listed above that cover the possible reasons you have already sliced into a bit in a formal way.



#5 Ryan_001   Prime Members   -  Reputation: 1409

Like
1Likes
Like

Posted 20 April 2014 - 05:02 PM

Actually, my link does list all the important information. As the notes say:

 

 

Objects of trivially-copyable types are the only C++ objects that may be safely copied with std::memcpy or serialized to/from binary files with std::ofstream::write()/std::ifstream::read(). In general, a trivially copyable type is any type for which the underlying bytes can be copied to an array of char or unsigned char and into a new object of the same type, and the resulting object would have the same value as the original.

 

The properties listed above that cover the possible reasons you have already sliced into a bit in a formal way.

 

A) I'm talking about std::move() vs std::memcpy(), not copying.  What you've posted covers copies, not moves.

B) I want to know WHY is shouldn't be done.



#6 Hodgman   Moderators   -  Reputation: 30432

Like
1Likes
Like

Posted 20 April 2014 - 06:36 PM

Are you talking in general, or concerning POD types only?

If in general, then you can't memcpy objects. If specifically POD, then they're likely equivalent.

[edit]i.e. You're not allowed to use memcpy on non-POD objects, so the question only applies to POD objects....
Assuming you're breaking the rules by memcpyijg non-POD objects, haven't you answered your own question -- any time that the move constructor does any real work (refcounting, swapping pointers, etc), then obviously this work won't be done by a memcpy.

#7 KulSeran   Members   -  Reputation: 2545

Like
4Likes
Like

Posted 20 April 2014 - 07:11 PM

std::move(T) is a cast that allows you to under some situations, left and right hand side of an assignment to transfer ownership of state without performing a deep copy.  For std::is_trivially_copyable objects, this still amounts to replicating the state. For more complex objects, it means state can 'move' between objects.

std::memcpy(Beg, End, Dest) is a function that allows you to replicate the state of an array of objects of a type such that std::is_trivially_copyable true.

std::copy(Beg, End, Dest) is a function that allows you to replicate the state of an array of objects of a type such that std::is_trivially_copyable is false.

std::move(Beg, End, Dest) is a function that allows you to perform std::move(T) over a range of elements, that implement move semantics.

 

So the difference is between:

#include <iostream>
#include <iterator>
#include <vector>

struct foo {
  int x;
  foo() : x(0) { }
};

struct bar {
  std::vector<int> y;
  bar() { }
  bar(int sz) : y(sz, 0) { }

  // The following 2 functions are not required (they have default implementations
  // This is just to be clear what is happening under the hood to help explain when move works.
  bar(bar &&other) { y = std::move(other.y); }
  bar &operator =(bar &&other) { y = std::move(other.y); return *this; }
};

int main(int argc, char **argv) {
  std::vector<foo> f(10);
  std::vector<bar> b(10, bar(10000));
  std::vector<foo> f2;
  std::vector<bar> b2;

  f2.reserve(f.size());
  b2.reserve(b2.size());

  bool doCopy = false;
  if (doCopy) {
    std::copy(f.begin(), f.end(), std::back_inserter(f2));
    std::copy(b.begin(), b.end(), std::back_inserter(b2));
  } else {
    std::move(f.begin(), f.end(), std::back_inserter(f2));
    std::move(b.begin(), b.end(), std::back_inserter(b2));
  }

  std::cout << b[0].y.size() << " " << b2[0].y.size() << std::endl;
}

You can safely std::copy or std::move the content of either of f -> f2 or b -> b2. 

In the case of f -> f2, it doesn't matter. You copy the elements either way.

 

In the case of b -> b2 copy and move do exactly as they imply.

Copy makes a copy.  Both 'b' and 'b2' contain a copy of the information (10 * 10000 * 2 ints in memory).

Move moves the data from 'b' to 'b2'.  Only 'b2' contains a copy of the information now because each vector in bar was "moved" to the associated vector in the new set of objects. (10 * 10000 ints in memory)


Edited by KulSeran, 20 April 2014 - 07:16 PM.


#8 Norman Barrows   Crossbones+   -  Reputation: 2158

Like
0Likes
Like

Posted 20 April 2014 - 07:13 PM


If specifically POD, then they're likely equivalent.

 

in pod land, as i recall, copy is a straight copy, and move is used for overlapping memory addresses (IE uses an intermediate buffer of some sort). since i never deal with overlapping addresses, i only use memcpy and then really only via ZeroMemory()

.  


Norm Barrows

Rockland Software Productions

"Building PC games since 1988"

 

rocklandsoftware.net

 


#9 Ryan_001   Prime Members   -  Reputation: 1409

Like
0Likes
Like

Posted 20 April 2014 - 09:00 PM

Clearly I'm not being clear.

 

I understand what a POD type is, and what it allows you to do.  I'm not talking about copying.  No copies.  Forget copying.  I'm only talking about move here.  I also understand what a copy constructor is, what a move constructor is, and why you would use one over another.

 

What I want to know is: under what circumstances would using std::memcpy() not yield the same results as std::move()?  Again, NOT copy, but move.  The ones that I can thinks of are:

 

1) std::move() throws an exception (either through the move constructor or the move assignment operator, whichever one you're using), which is generally considered bad, like destructor throwing bad.

2) in the case of hardware mapped memory, like an object place-constructed in a particular segment of memory by the OS

3) where a move constructor (or assignment operator) is intentionally deleted or not accessible, but again this kinda leads back into the original question, cause I can't think of any situation where copy would be allowed but moving disabled apart from 2

4) where the src object of a move has to (or can be) re-used

 

I understand that std::move(), std::memcpy(), and normal copying all copy bytes around.  But in the context of C++ a move is different than a copy.  So yes I am talking about std::memcpy(), but I'm talking about move semantics not copy semantics.  POD types and std::is_trivially_copyable refer to copy semantics.  For example a class like:

struct Object { 
   char* data; 
   Object () : data(new char[10]);
   virtual ~Object () { delete[] data; }
   };

is not trivially copyable (you'd have two objects pointing to the same data on the heap), but... is it trivially moveable?  There are no exceptions thrown.  The pointer will move properly with a std::memcpy(), as there will be still only one owner.  As long as the src isn't destructed we don't have a data leak.  Does the hidden v-table pointer get copied properly?  Will something else get mangled?

 

I hope that makes my question clearer.



#10 SiCrane   Moderators   -  Reputation: 9604

Like
6Likes
Like

Posted 20 April 2014 - 09:22 PM

One situation where memcpy() won't work but std::move() will is when the object's constructors and destructor handle self-registration. For example, if it registers itself as part of the root set to a garbage collector by giving the garbage collector it's address. If you just copy the the memory without updating the root set for the GC, the GC will be trying to use the old memory location for its sweeps.



#11 King Mir   Members   -  Reputation: 2002

Like
3Likes
Like

Posted 20 April 2014 - 10:43 PM

In adition to SiCrane's example, another thing that could complicate moving is pointers into the object itself. For example, a small vector might have a pointer that points within the object when there are few members, and to allocated memory when there are lots. That pointer would need to be updated to point within the moved object.

 

I understand that std::move(), std::memcpy(), and normal copying all copy bytes around.  But in the context of C++ a move is different than a copy.  So yes I am talking about std::memcpy(), but I'm talking about move semantics not copy semantics.  POD types and std::is_trivially_copyable refer to copy semantics.  For example a class like:
struct Object { 
   char* data; 
   Object () : data(new char[10]);
   virtual ~Object () { delete[] data; }
   };

is not trivially copyable (you'd have two objects pointing to the same data on the heap), but... is it trivially moveable?  There are no exceptions thrown.  The pointer will move properly with a std::memcpy(), as there will be still only one owner.  As long as the src isn't destructed we don't have a data leak.  Does the hidden v-table pointer get copied properly?  Will something else get mangled?

 

I hope that makes my question clearer.

One of the things that a move constructor needs to do is leave the source object in a destructable state. So a just a memmove wouldn't be enough here.



#12 iMalc   Crossbones+   -  Reputation: 2306

Like
4Likes
Like

Posted 20 April 2014 - 10:52 PM

The obvious example that comes to mind, is when the object contains a pointer to one of its other members. I have just been using production code that has exactly that.

 

The other thing you should not overlook is that memcpy-ing may do most of the job for some types, but it doesn't in itself suppress destruction of the object where the data came from, which you would need to also do to avoid double-deletion. std::move on the other hand, gets implemented to null out pointers from the source location as necessary, causing subsequent destruction to be safe.


"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

#13 Ryan_001   Prime Members   -  Reputation: 1409

Like
0Likes
Like

Posted 21 April 2014 - 06:18 AM

One situation where memcpy() won't work but std::move() will is when the object's constructors and destructor handle self-registration. For example, if it registers itself as part of the root set to a garbage collector by giving the garbage collector it's address. If you just copy the the memory without updating the root set for the GC, the GC will be trying to use the old memory location for its sweeps.

Didn't think of that one, thank-you.
 

In adition to SiCrane's example, another thing that could complicate moving is pointers into the object itself. For example, a small vector might have a pointer that points within the object when there are few members, and to allocated memory when there are lots. That pointer would need to be updated to point within the moved object.

Another good point.

#14 Kian   Members   -  Reputation: 238

Like
2Likes
Like

Posted 21 April 2014 - 12:49 PM

I'm not sure if someone mentioned this, but a clear difference when your class allocates memory is that std::memcpy will not remove ownership from the original. So when your object dies, it will release the memory, but the object you copied will still hold a pointer to the released memory, causing a crash down the road. std::move, on the other hand, will release ownership from the original object (zeroing pointers, etc) so that it can be destroyed without affecting the state of the target object.

 

A move affects both the source and the destination, while a copy only affects the destination.



#15 achild   Crossbones+   -  Reputation: 1886

Like
1Likes
Like

Posted 22 April 2014 - 10:27 AM

I'm not sure if someone mentioned this, but a clear difference when your class allocates memory is that std::memcpy will not remove ownership from the original. So when your object dies, it will release the memory, but the object you copied will still hold a pointer to the released memory, causing a crash down the road. std::move, on the other hand, will release ownership from the original object (zeroing pointers, etc) so that it can be destroyed without affecting the state of the target object.

 

A move affects both the source and the destination, while a copy only affects the destination.

 

Exactly.

 

 


struct Object {
char* data;
Object () : data(new char[10]);
virtual ~Object () { delete[] data; }
};
is not trivially copyable (you'd have two objects pointing to the same data on the heap), but... is it trivially moveable?  There are no exceptions thrown.  The pointer will move properly with a std::memcpy(), as there will be still only one owner.  As long as the src isn't destructed we don't have a data leak.  Does the hidden v-table pointer get copied properly?  Will something else get mangled?

 

The pointer will NOT move properly. It gets copied properly. There are now 2 owners, not 1. You said "as long as src isn't destructed" but that's an important detail that highlights a major difference between memcpy and move.


Edited by achild, 22 April 2014 - 10:27 AM.





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS