Безопасность static_cast для указателя на производный класс от базового деструктора

Это вариант вопросов Downcasting с использованием Static_cast в C++ и Безопасность недопустимого downcast с использованием static_cast (или reinterpret_cast) для наследования без добавления членов

Мне неясно фраза в стандарте "B, которая на самом деле является подобъектом объекта типа D, результирующий указатель указывает на включающий объект типа D" относительно поведения в ~B. Если вы приведете к D в ~B, это все еще подобъект в этой точке? Следующий простой пример показывает вопрос:

void f(B* b);

class B {
public:
  B() {}
  ~B() { f(this); }
};

class D : public B { public: D() {} };

std::set<D*> ds;

void f(B* b) {
  D* d = static_cast<D*>(b);  // UB or subobject of type D?
  ds.erase(d);
}

Я знаю, что приведение является открытой дверью к катастрофе, и делать что-либо подобное с помощью dtor - плохая идея, но сотрудник заявляет: "Код действителен и работает правильно. Этот приведенный код совершенно действителен. что это не должно быть разыменовано ".

Я указал, что приведение не является необходимым, и мы должны предпочесть защиту, предоставляемую системой типов, комментариям. Грустная часть в том, что он один из старших / ведущих разработчиков и предполагаемый "эксперт" по C++.

Могу я сказать ему, что актерский состав - UB?

3 ответа

Решение

[Expr.static.cast]/ P11:

Значение типа "указатель на cv1" B, "Где B - тип класса, может быть преобразован в тип значения" указатель на cv2 " D," где D класс, производный (раздел 10) от B, если действительное стандартное преобразование из "указателя на D На указатель на B ”(4.10), cv2 - это та же квалификация cv, что и cv1, или более высокая квалификация, чем cv1, и B не является ни виртуальным базовым классом D ни базовый класс виртуального базового класса D, Значение нулевого указателя (4.10) преобразуется в значение нулевого указателя типа назначения. Если значение типа "указатель на cv1" B Указывает на B это на самом деле подобъект объекта типа D результирующий указатель указывает на включающий объект типа D, В противном случае поведение не определено.

Вопрос в том, является ли во время static_cast указатель фактически указывает на B это на самом деле подобъект объекта типа D Msgstr "Если так, UB не существует; если нет, то поведение не определено независимо от того, используется ли разыменованный указатель или нет.

[class.dtor] / p15 говорит, что (выделение мое)

Как только деструктор вызывается для объекта, объект больше не существует

и [basic.life]/p1 говорит, что

Время жизни объекта типа T заканчивается, когда:

  • если T является типом класса с нетривиальным деструктором (12.4), начинается вызов деструктора, или
  • [...]

Отсюда D Срок службы объекта закончился, как только вызывается его деструктор, и, конечно, к тому времени B деструктор начал казнить D Труп деструктора закончил выполнение. На данный момент нет "объекта типа D оставил что это B может быть подобъектом - он "больше не существует". Таким образом, у вас есть UB.

Clang с UBsan сообщит об ошибке в этом коде, если B сделан полиморфным (учитывая виртуальную функцию), который поддерживает это чтение.

Видимо, у вашего коллеги сложилось впечатление, что, если вы не разыменуете неверный указатель, у вас все в порядке.

Он не прав.

Простая оценка такого указателя имеет неопределенное поведение. Этот код явно не работает.

Вы должны окончательно сказать ему, что это UB!!

Зачем?

12.4 / 7: Основания и элементы уничтожаются в порядке, обратном завершению их конструктора. Объекты разрушаются в порядке, обратном их конструкции.

12.6.2 / 10: сначала инициализируются (...) виртуальные базовые классы (...), затем инициализируются прямые базовые классы

Таким образом, при разрушении D сначала уничтожаются члены D, а затем субобъект D, и только тогда будет разрушаться B.

Этот код гарантирует, что f() вызывается, когда объект B уничтожен:

 ~B() { f(this); } 

Поэтому, когда объект D уничтожается, сначала уничтожается подобъект D, а затем выполняется ~B(), вызывая f(),

В f() вы приводите указатель на B как указатель на D. Это UB:

3.8 / 5: (...) после окончания срока службы объекта и до повторного использования или освобождения хранилища, которое занимал объект, может использоваться любой указатель, который ссылается на место хранения, где будет или будет находиться объект, но только ограниченным образом. (...) Программа имеет неопределенное поведение, если указатель используется для доступа к нестатическому члену данных или вызова нестатической функции-члена объекта, или (...) указатель используется в качестве операнда static_cast.

Другие вопросы по тегам