Ответ 1
Во-первых, стандартный отказ от ответственности: это поведение undefined, поэтому даже с одним конкретным компилятором изменение флагов компилятора, дня недели или способа просмотра компьютера может изменить поведение.
Следующее все предполагает, что у вас есть какие-то, по крайней мере, незначительные нетривиальные разрушения в ваших деструкторах (например, объекты удаляют некоторую память или содержат другие объекты, которые сами удаляют некоторую память).
В простом случае (одиночное наследование) вы обычно получаете что-то примерно эквивалентное статической привязке, то есть если вы уничтожаете производный объект с помощью указателя на базовый объект, вызывается только базовый конструктор, поэтому объект isn ' t уничтожен должным образом.
Если вы используете множественное наследование и уничтожаете объект производного класса через "первый" базовый класс, он обычно будет примерно таким же, как если бы вы использовали одиночное наследование - будет вызван деструктор базового класса, но деструктор производного класса не будет.
Если у вас есть множественное наследование и уничтожить производный объект с помощью указателя на второй (или последующий) базовый класс, ваша программа, как правило, сбой. При множественном наследовании у вас есть несколько объектов базового класса при множественных смещениях в производном объекте.
В типичном случае первый базовый класс будет находиться в начале производного объекта, поэтому использование адреса производного как указателя на первый объект базового класса будет примерно таким же, как в случае одиночного наследования - мы получить эквивалент статической привязки/статической отправки.
Если мы попробуем это с любым из других базовых классов, указатель на производный не укажет на объект этого базового класса. Указатель должен быть настроен так, чтобы указывать на второй (или последующий) базовый класс, прежде чем он может быть использован как указатель на этот тип объекта вообще.
С не виртуальным деструктором, что обычно произойдет, так это то, что код будет в основном принимать этот адрес этого первого объекта базового класса, примерно примерно эквивалент reinterpret_cast
на нем и попытаться использовать эту память как если это был объект базового класса, указанный указателем (например, base2). Например, пусть предположим, что base2 имеет указатель со смещением 14, а dest2-объект base2 пытается удалить блок памяти, на который он указывает. С не виртуальным деструктором он, вероятно, получит указатель на объект base1, но он все равно будет смотреть на смещение 14 и попытаться рассматривать это как указатель и передать его в delete
. Возможно, что base1 содержит указатель на это смещение и фактически указывает на некоторую динамически распределенную память, и в этом случае это может показаться успешным. Опять же, также может быть, что это нечто совсем другое, и программа умирает с сообщением об ошибке (например), пытающимся освободить недействительный указатель.
Также возможно, что base1 меньше размера 14 байтов, поэтому это фактически приводит к смещению (скажем) смещения 4 в base2.
Итог: для случая, подобного этому, все становится очень уродливым в спешке. Самое лучшее, на что вы можете надеяться, это то, что программа быстро и громко умирает.
Просто для пинков, быстрый демонстрационный код:
#include <iostream>
#include <string>
#include <vector>
class base{
char *data;
std::string s;
std::vector<int> v;
public:
base() { data = new char; v.push_back(1); s.push_back('a'); }
~base() { std::cout << "~base\n"; delete data; }
};
class base2 {
char *data2;
public:
base2() : data2(new char) {}
~base2() { std::cout << "~base2\n"; delete data2; }
};
class derived : public base, public base2 {
char *more_data;
public:
derived() : more_data(new char) {}
~derived() { std::cout << "~derived\n"; delete more_data; }
};
int main() {
base2 *b = new derived;
delete b;
}
g++/Linux: ошибка сегментации
clang/Linux: ошибка сегментации
VС++/Windows: всплывающее окно: "foo.exe перестает работать" "Проблема привела к тому, что программа перестала работать правильно. Закройте программу."
Если мы изменим указатель на base
вместо base2
, мы получим ~base
от всех компиляторов (и если мы получим только один базовый класс и будем использовать указатель на этот базовый класс, мы получим то же: работает только деструктор базового класса.