For certain reasons, I need to call placement new on an already correctly created instance , but with its VPTR
incorrect . I have obtained it from a file, from a socket , ... the method does not matter.
It's a class with member-functions virtual
, so I need to call the constructor to properly initialize the VPTR
.
However, the constructor initializes all member-variables. If it was primitive data , I wouldn't have a problem. But they are not . Some of the member-variables have their own constructor.
Thinking about it, I found a solution... Call the copy constructor on the instance itself!
#include <iostream>
struct Dato {
int x = 0,
y = 0;
};
struct Padre {
Dato datos;
virtual ~Padre( ) {
}
};
struct Hija : public Padre {
};
int main( ) {
Hija h;
h.datos.x = 10;
h.datos.y = 20;
Hija *ptr = new ( &h ) Hija( h );
std::cout << "x: " << ptr->datos.x << ", y: " << ptr->datos.y << '\n';
return 0;
}
The example is available at Wandbox.
The result obtained is the one sought:
x: 10, y: 20
The questions:
How portable is this?
I understand that a recursive call is made to the various constructors involved. Is there another method that gives the same result (initialize the
VPTR
without overwriting the rest of the memory), bypassing those calls?
I am convinced that your proposal results in undefined behavior and, consequently, it would not be portable. I have searched for the relevant quotes from standard 1 to validate my argument (translation and highlighting mine).
Regarding the lifecycle, nothing happens if you overwrite an existing object with another, as long as the object has a trivial destructor; otherwise the behavior is undefined. Is it your case?
So in your case, you are forcing undefined behavior since:
You end the life cycle of an object, by reusing its space:
The destructor of
Hija
is non-trivial since its direct base class lacks a trivial destructor because in addition to being a virtual destructor, it is provided by the user.By engaging in undefined behavior, the code might be portable, it might not be portable, it might be both, and it might be neither.
If you have retrieved an object but the virtual information is incorrect, I understand that a raw binary dump of the object's memory has been made and therefore what you have is basically garbage.
Why?
The first reason is that if the memory of an object is flushed, if it makes use of dynamic memory (it does not matter if it is the object directly or an internal object of the object), it is quite likely that this memory is not being used. dumping which will cause a malfunction of the program.
On the other hand, virtuality management is left to compilers so the source code of a class can generate two incompatible binaries when using different compilers.
Imagine the following scenario:
Compiler A: Stores all information regarding virtuality in a single region of memory. Access to that memory is done with a pointer that the compiler inserts at the start of the object.
Compiler B: Generates a table of virtual functions and, additionally, manages a parallel table with relevant information for type conversions (
dynamic_cast
). That's two pointers that are inserted at the start of the object.In these contexts the data referring to the state of the object is not aligned in any way and any attempt to force the virtuality information to be restored will not succeed in fixing the state of the object.
And we must not lose sight of the fact that binary information is sensitive to the architecture of the equipment: big-endian vs. little-endian. Incompatible architectures versus binary data.
The best solution is to serialize the state of the object and send and store only that state. Binary storage of an object can only give you problems as soon as multiple computers with different hardware/software are involved.
I am aware that serializing objects is more work, but no one said that programming was going to be easy and beautiful... well, okay, yes, it's something that sells... but it's a lie!!!