Невиртуальный деструктор и clang

Всем привет. Есть вот такой абстрактный класс, в котором я осознанно сделал деструктор не виртуальным:

#include <iostream>

#ifdef _MSC_VER
    #define __PRETTY_FUNCTION__ __FUNCSIG__
#endif

class Base
{
public:
    
    virtual void foo() = 0;
    
    ~Base()
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

Есть потомок:

class Derived : public Base
{
public:
    virtual void foo() override
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
    
    ~Derived()
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

И вызов деструктора:

Base * derived = new Derived;
delete derived;

Компилятор и запуск:

$clang --version

Apple clang version 12.0.0 (clang-1200.0.32.28)
Target: x86_64-apple-darwin19.6.0
Thread model: posix

$clang -lstdc++ -std=c++11 example.cpp -o example.out

Ожидаемое поведение этого кода — вызов деструктора только базового класса, так как вызов, очевидно, не виртуальный. В моем случае я получаю исключение на строке определения ~Base():

EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

Опытным путем было выяснено, что проблема возникает если класс абстрактный. Если сделать метод foo виртуальным (virtual void foo() {};) то результат кода предсказуем:

Base::~Base()

Это баг компилятора или ожидаемое поведение? Если второе, то в чем смысл?


Ответы (2 шт):

Автор решения: HolyBlackCat

Такой вызов деструктора вызывает неопределенное поведение. Не гарантируется никакое "будет вызван только деструктор родителя" и т. п.

в чем смысл?

Ну, видимо Clang так помогает вам обнаружить неопределенное поведение - и правильно делает.


Еще пример в тему:

struct A {int x = 0;};
struct B {int y = 0;};
struct C : A, B {};

int main()
{
    B *b = new C;
    delete b;
}

Вылетает с free(): invalid pointer. Домашнее задание - подумать, почему так происходит.

→ Ссылка
Автор решения: AR Hovsepyan
  1. Если деструктор не виртуальный, вы даете команду: "уничтожить объект базового класса(базовую часть объекта) по адресу derived". А как уничтожать объект, который не возможно создавать, так как класс абстрактный?..(грубая ошибка). А если для наследника нужна память больше? Тогда и при условии, что базовый класс не абстрактный, это ошибка, связанная с не полным освобождением захваченной памяти.
  2. Если деструктор виртуальный, "уничтожаем объект по адресу derived, каков бы он не был". Это и есть правильный подход(иметь виртуальный деструктор в абстрактном классе) .
→ Ссылка