0

This is more of a 'what if' question than a practical one. Assume that I allocate some polymorphic objects in such a way that the allocator always reserves enough room for the largest object. I want to know if you can use memcpy to change the polymorphic type of one object to another.

Out of my head; not tested:

class Animal {
  virtual ~Animal() {}
};

struct Cat : public Animal {
  int lives;
};

struct Dog : public Animal {
  size_t bark_count;
};

Dog* dog = malloc(std::max(sizeof(Dog), sizeof(Cat)));
new (dog) Dog(0);

Cat* cat = malloc(std::max(sizeof(Dog), sizeof(Cat)));
new (cat) Cat(9);

// Make cat a dog. Is this possible?
memcpy(reinterpret_cast<char*>(cat), reinterpret_cast<char*>(dog), sizeof(Dog));

I'm assuming that due to the question When is a type in c++11 allowed to be memcpyed?, this is invalid behavior. However, I'm not 100% sure, either.

Context: I'm writing a type checker and need to 'rewrite' types as more information comes available. I thought about refactoring the Type class to a union type but it will require a considerable amount of work. Other approaches are very much welcome.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
samvv
  • 1,934
  • 1
  • 18
  • 26
  • Only objects with implicit lifetimes can be created this way see [Object creation](https://en.cppreference.com/w/cpp/language/object#Object_creation) and [ImplicitLifetimeType](https://en.cppreference.com/w/cpp/named_req/ImplicitLifetimeType) – Richard Critten Jun 17 '23 at 09:57
  • When it is [triviably copyable](https://en.cppreference.com/w/cpp/types/is_trivially_copyable). For me this is not even a language laywer question because this is actually very well defined (no gray areas, no known compiler bugs) – Pepijn Kramer Jun 17 '23 at 09:57
  • ... otherwise, it has UB – Ted Lyngmo Jun 17 '23 at 09:57
  • 1
    Most times when developers ask this is because they are investigating binary serialization (loading/saving from memory to disk/network whatever). It is not a trivial thing to do right. So tell us if this is what you are exploring. – Pepijn Kramer Jun 17 '23 at 09:58
  • @PepijnKramer See the last paragraph. I'm writing a type checker that needs to rewrite types in memory. – samvv Jun 17 '23 at 10:00
  • Then I will refer you to : [std::bit_cast](https://en.cppreference.com/w/cpp/numeric/bit_cast). But still when reinterpreting a piece of memory as another type be careful. Make sure constructors are called and/or lifetime of objects are started. – Pepijn Kramer Jun 17 '23 at 10:00
  • @PepijnKramer Ooh that's actually very interesting, thanks! – samvv Jun 17 '23 at 10:02
  • 1
    Note bit_cast will NOT end the life time of the previous object... so it might not what you are looking for. But it will give you some insights. – Pepijn Kramer Jun 17 '23 at 10:03
  • Looks like you will have to make explicit conversion functions (or constructors). Which is in the end not a bad thing. – Pepijn Kramer Jun 17 '23 at 10:09
  • @PepijnKramer yeah I was afraid of that ... Ah well I guess I know what to do this Saturday ☺️ – samvv Jun 17 '23 at 10:12
  • *Would overwriting a polymorphic object byte-by-byte in memory ever be valid in C++?* **No**. – Eljay Jun 17 '23 at 12:57
  • Why don't you simply use the `asm` keyword? – curiousguy Jun 17 '23 at 21:21

1 Answers1

4

I'm assuming that due to the question When is a type in c++11 allowed to be memcpyed?, this is invalid behavior. However, I'm not 100% sure, either.

Exactly.

Your class is not trivially-copyable due to having a non-trivial destructor because it is virtual.

Therefore, copying the object representation with memcpy isn't possible at all. Even worse, the types are also not implicit-lifetime and therefore memcpy won't create any Dog object in the cat storage at all. The behavior will be undefined, at a minimum if you actually use the destination buffer as if it contained the Dog object. The memcpy itself may be ok with some nuances to how exactly you have written the example.

Writing object representation byte-by-byte through character-like pointers isn't covered by the standard currently either way. memcpy is just specified as a "magical" function that somehow copies underlying bytes of an object in such a way that if all underlying bytes of a trivially-copyable object were copied into an object of the same type (which may be implicitly-created by memcpy), that then the destination object will hold the same value as the source object.


reinterpret_cast<char*> is pointless btw. memcpy wants a void* and the conversion to it is implicit.

user17732522
  • 53,019
  • 2
  • 56
  • 105