Дыра в системе типов - это случай, когда компилятор не перехватывает, когда тип приводится к другому несовместимому типу.
Представьте, что у вас есть два простых класса:
class A
{
char i;
};
class B : public A
{
char j;
};
Давайте для простоты проигнорируем такие вещи, как заполнение и т. Д. И предположим, что объекты типа A
имеют размер 1 байт, а объекты типа B
имеют размер 2 байта.
Теперь, когда у вас есть массив типа A
или массив типа B
, они будут выглядеть так:
A a[4]:
=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i |
=================
B b[4]:
=================================
| 0 | 1 | 2 | 3 |
|-------|-------|-------|-------|
| i | j | i | j | i | j | i | j |
=================================
Теперь представьте, что у вас есть указатели на эти массивы, а затем приведите один к другому, это, очевидно, приведет к проблемам:
a cast to B[4]:
=================================
| 0 | 1 | 2 | 3 |
|-------|-------|-------|-------|
| i | j | i | j | x | x | x | x |
=================================
Первые два объекта в массиве будут интерпретировать i
член 2-го и 4-го A
как их j
член. 2-й и 3-й член получают доступ к нераспределенной памяти.
b cast to A[4]:
=================
| 0 | 1 | 2 | 3 |
|-------|-------|
| i | i | i | i | x | x | x | x |
=================
Здесь все наоборот: все 4 объекта теперь поочередно интерпретируют i
и j
из 2 B
экземпляров как свои i
члены. И половина массива потеряна.
Теперь представьте себе удаление такого приведенного массива. Какие деструкторы будут называться? Какая память будет освобождена? В этот момент вы находитесь в глубоком аду.
Но подождите, это еще не все.
Представьте, что у вас есть 3 класса, как это:
class A
{
char i;
};
class B1 : public A
{
float j;
};
class B2 : public A
{
int k;
};
А теперь вы создаете массив B1
указателей:
B1* b1[4];
Если вы приведете этот массив к массиву A
указателей, вы можете подумать, "ну, это хорошо, верно" ?
A** a = <evil_cast_shenanigans>(b1);
Я имею в виду, вы можете безопасно получить доступ к каждому члену в качестве указателя на A
:
char foo = a[0]->i; // This is valid
Но что вы также можете сделать, это:
a[0] = new B2{}; // Uh, oh.
Это допустимое назначение, ни один компилятор не будет жаловаться, но вы не должны забывать, что мы фактически работаем над массивом, который был создан как массив указателей на B1
объектов. И его первый член теперь указывает на объект B2
, к которому теперь можно получить доступ как B1
, и компилятор ничего не сказал.
float bar = b1[0]->j; // Ouch.
Итак, вы снова в глубоком аду, и компилятор не сможет вас предупредить, за исключением случаев, когда это обновление запрещено в первую очередь.
Почему API-интерфейс std :: unique_ptr запрещает преобразование производных указателей в базовые?
Надеюсь, приведенные выше объяснения дают веские причины.
Как это может запретить преобразования?
Он просто не предоставляет API для конвертации. API shared_ptr имеет функции преобразования, такие как static_pointer_cast
, API unique_ptr - нет.