2015-03-08 2 views
6

Это вариант вопросов Downcasting using the Static_cast in C++ и Safety of invalid downcast using static_cast (or reinterpret_cast) for inheritance without added membersбезопасности static_cast указателя к производному классу от базового деструктора

Я не ясно, на фразе в стандарте «В том, что на самом деле подобъекте объекта типа 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?

+2

Я _think_ это UB, но я не уверен. Тем не менее, код [по крайней мере, в этом примере] абсолютно пахнет хуже моих носков после двух недель в жаркое лето без мытья ... Правильная вещь здесь была бы иметь дескриптор в 'D', который удалял объект из' ds' - это не должно быть сделано в 'B'. Это, конечно же, также позволит избежать проблем с UB. Тот факт, что он действительно может работать и хорошо определен, не имеет значения. Или сделайте 'ds' в' std :: set bs' ... –

+0

До тех пор, пока не существует других классов, полученных из B и D, не имеет дополнительных членов, это может сработать. Но все это пахнет. Как вы можете убедиться, что каждый B - это D, и если это точно, почему у вас будут разные классы в первую очередь? –

+0

@MatsPetersson: Почему вы думаете, что это UB? Стандарт в 5.2.9 дает пример, почти равный OP, хотя он использует ссылки: 'struct B {}; struct D: public B {}; D d; B & br = d; static_cast (br); 'Конечно, это только теоретический вопрос. Код в вопросе OP ужасен. –

ответ

5

[expr.static.cast]/P11:

prvalue типа «указатель на CV1B», где В представляет собой тип класса, может быть преобразован в prvalue из типа «указатель на CV2D», где D является класс, производный (пункт 10) от B, если действительный стандартного преобразования из «указатель на D», чтобы «указатель на B»существует (4.10), CV2 является же сорта-квалификация, как, или больше резюме-квалификации, чем, CV1 и B не является ни виртуальным базовым классом D, ни базового класса виртуальной базы класс D. Значение нулевого указателя (4.10) преобразуется в значение нулевого указателя для типа назначения. Если prvalue из типа «указатель на cv1B» указывает на B, что на самом деле является подобъектом объекта типа D, в результате указатель указывает на ограждающих объект типа D. В противном случае поведение не определено.

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

[class.dtor]/р15 говорит, что (курсив мой)

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

и [основной. жизнь]/p1 говорит, что

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

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

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

Clang with UBsan будет report an error по этому коду, если B сделан полиморфным (с учетом виртуальной функции), который поддерживает это чтение.

+0

", если существует допустимое стандартное преобразование из« указателя на D »в« указатель на B »» - см. 12.7/3: это объясняет, что это преобразование указателя действителен до тех пор, пока «уничтожение этих классов не будет иметь ** завершено ** "и UB otherwhise. Поскольку здесь деструкция D завершена, и начался один из B, это окончательно UB! – Christophe

+0

@Christophe «указатель на D» и «указатель на B» являются типами, и это предложение имеет дело с типами, а не с значениями. «Допустимое стандартное преобразование из« указателя на D »в« указатель на B »существует», пока B является доступной и однозначной базой 'D' (см. [Conv.ptr]/p3). –

+0

Для моего собственного понимания: если объект больше не существует после того, как деструктор имеет startet, почему я могу называть какую-либо функцию очистки внутри него? –

0

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

Почему?

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

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

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

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

~B() { f(this); } 

Таким образом, когда объект D разрушен, D suboject разрушается, а затем ~ В() выполняется, вызывающий f().

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

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

+1

"статический кастинг указателя не коснется самого объекта. Таким образом, это утверждение не является UB в том смысле, что ничего неопределенного не произойдет немедленно". Нет, 'static_cast' сам может быть UB. –

+0

Действительно, 'static_cast' на том, что на самом деле не такого типа, это UB. 'reinterpret_cast' будет в порядке, если он не разыменован. –

+1

@ T.C. Ой! Вы совершенно правы! Я думал, что только разыгрывание каста-лейтенанта было UB, но после вашего замечания я нашел цитату из стандарта, которая явно адресует static_casting после конца жизни объекта. Я отредактировал сообщение: больше не сомневаюсь: это UB! Спасибо за подсказку! – Christophe

2

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

He is Неверный.

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

+0

Это определение не определено, но не определено, но при реализации, описанном в реализации, добавлена ​​сноска, в которой говорится: «Некоторые реализации могут определять, что копирование недопустимого значения указателя вызывает системную ошибку выполнения». Тем не менее, указатель не становится недействительным до тех пор, пока функция освобождения не будет запущена. Указатели на неинициализированное хранилище остаются действительными указателями, и хранилище остается доступным до тех пор, пока не будет запущена функция освобождения. Я думаю, что ответ T.C. дает более убедительный аргумент, что код, тем не менее, имеет UB. – hvd