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

Started by
13 comments, last by popsoftheyear 9 years, 11 months ago

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().?

Advertisement

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

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?

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.

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.

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.

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)


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 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

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.

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.

This topic is closed to new replies.

Advertisement