The following code snippet causes a segmentation fault during the dynamic_cast
. Can anybody explain to me why this happens?
#include <cassert>
#include <cstdint>
uint8_t buffer[32];
class Top {
public:
virtual ~Top() = default;
};
template <typename T>
class Middle : public Top {
public:
void copy() {
// Create copy of class instance in buffer
auto memory{reinterpret_cast<T *>(buffer)};
*memory = *dynamic_cast<T *>(this);
// Upcast, works
Top * topPtr{memory};
assert(topPtr != nullptr);
// Downcast, causes segmentation fault, why?
auto bottomPtr{dynamic_cast<T *>(topPtr)};
}
};
class Bottom : public Middle<Bottom> {
};
int main() {
Bottom b;
b.copy();
}
Thanks.
CodePudding user response:
auto memory{reinterpret_cast<T *>(buffer)};
*memory = *dynamic_cast<T *>(this);
This is incorrect approach, you cannot just interpret some bytes as T
. Objects are created only by calling the appropriate constructors which for example initialize the virtual jump table.
The second line is also wrong even in your context. It calls the assignment operator, that rightfully assumes its this
is an alive object.
The correct way is using placement new
with correctly aligned storage, for example std::aligned_storage_t<sizeof(T),alignof(T)>
.
Working example:
#include <cstdint>
#include <cassert>
#include <type_traits>
#include <new>
class Top {
public:
virtual ~Top() = default;
};
template <typename T>
class Middle : public Top {
public:
void copy() {
std::aligned_storage_t<sizeof(T),alignof(T)> buffer;
// Create a new T object. Assume default construction.
auto* memory = new(&buffer)T();
// Copy the object using operator=(const T&)
*memory = *dynamic_cast<T *>(this);
// Upcast, works
Top * topPtr{memory};
assert(topPtr != nullptr);
// Downcast also works.
auto* bottomPtr{dynamic_cast<T *>(topPtr)};
// Using placement new requires explicit call to destructor.
memory->~T();
}
};
class Bottom : public Middle<Bottom> {
};
int main() {
Bottom b;
b.copy();
}
- Lifetime begins with a constructor call, you cannot get around that, if it needs parameters, you have to pass them.
operator=
is the correct approach to copy objects, do not usestd::memcpy
unless you really know what you are doing.- Objects created by placement new require explicit call to their destructor.
- Please do not hide pointers behind
auto
,typedef
,orusing
with exception of opaque handles.