API std :: unique_ptr <T []> запрещает преобразование производных указателей в базовые - PullRequest
0 голосов
/ 28 августа 2018

В Modern Effective C ++ , «Iterm 19: Используйте std::shared_ptr для управления ресурсами с общим владением». На странице 133-134 написано:

std :: shared_ptr поддерживает преобразования указателей из производных в базу, которые делают смысл для отдельных объектов, но это открывает дыры в системе типов, когда применяется к массивам. (По этой причине API-интерфейс std :: unique_ptr запрещает такие преобразования.)

Что означает "открытые дыры в системе типов"?

Почему std::unique_ptr<T[]> API запрещает преобразование производных указателей в базовые?

А как это могло запретить преобразования?

1 Ответ

0 голосов
/ 03 сентября 2018

Дыра в системе типов - это случай, когда компилятор не перехватывает, когда тип приводится к другому несовместимому типу.

Представьте, что у вас есть два простых класса:

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 - нет.

...