Search code examples
c++copypolymorphismdynamic-cast

dynamic_cast on polymorphic class copy segfaults


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.


Solution

  • 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 use std::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,or using with exception of opaque handles.