
Сообщение от
IIARTEMII
Что это за механизм такой, который реагирует на отсутствие виртуального деструктора, если по сути это приводит к утечке памяти, а не к программной ошибке (да, UB может привести к ошибке, а может и нет, но...)? Почему этот механизм себя проявляет только в MSVS?
Тут очень многое зависит от деталей реализации - от того, как сделаны
виртуальные таблицы в данном конкретном компиляторе и как устроен его
аллокатор памяти.
Приключения начинаются вот в этой строке:
Если Base и Der - обычные классы, не отягощенные virtual-методами,
включая деструктор, то при касте указателя Der к Base его скалярное
значение сохранится. То есть, к примеру, если "new Der" вернет адрес
0xABCDE8, то и в ptr будет записано 0xABCDE8. В этом случае, когда
будет вызван "delete ptr", аллокатор получит все тот же адрес и
сможет корректно освободить занимаемую объектом Der память.
Хотя деструктор Der вызван не будет.
Предположим, Der унаследован не только от Base, но еще и от Megabase,
причем Megabase в списке наследования расположен раньше Base:
C++ |
1
| class Der : public Megabase, public Base { ... }; |
|
В этом случае приведение указателя от Der к Base приведет к
изменению его скалярного значения.
Пример:
C++ |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| #include <iostream>
struct Megabase
{
virtual void somefunc() {}
};
struct Base
{
virtual void anotherfunc() {}
};
struct Der : public Megabase, public Base {};
int main()
{
using namespace std;
Der * pDer = new Der();
Base * pBase = pDer;
cout << pDer << endl;
cout << pBase << endl;
// delete pBase; // ???
return 0;
} |
|
Вот тут и начинаются глюки. "delete pBase" приведет к попытке
освобождения памяти по адресу 0x985043c, хотя при создании объекта
Der был возвращен адрес 0x9850438. Это равноценно тому, что в
delete передали адрес, который никогда не выделялся.
Кстати, возникновение ошибки в данном примере характерно не только
для MS Visual C++, можете проверить на любом компиляторе, который
есть под рукой.
Нечто похожее происходит и при виртуальном наследовании, так как
там "раскладка" классов в памяти заметно отличается от обычной,
хотя все это, повторюсь, очень compiler-specific:
C++ |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| #include <iostream>
struct parent
{
int a;
};
struct child : public virtual parent
{
int b;
};
int main()
{
using namespace std;
child Child;
cout << &Child.a << endl;
cout << &Child.b << endl;
return 0;
} |
|
Неожиданно: a размещается в памяти после b.
При обычном (невиртуальном) наследовании все было бы наоборот.
И последний пример, снова с изменением скалярного значения указателя
при касте к базовому классу:
C++ |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include <iostream>
struct parent
{
int a;
};
struct child : public virtual parent
{
int b;
};
int main()
{
using namespace std;
child * pChild = new child();
parent * pParent = pChild;
cout << pChild << endl;
cout << pParent << endl;
// delete pParent; // ???
return 0;
} |
|
При полиморфном удалении объекта, когда базовый класс имеет виртуальный
деструктор, аллокатор всегда получает "правильный" адрес памяти для очистки,
так что проблем, описанных выше, не возникает.