|
Программирование >> Полиморфизм без виртуальных функций в с++
D* pdl = dynamic cast<D*>(pb); D* pd2 = static cast<D*>(pb); Если в действительности pb указывает на D, то pdl и pd2 получают одно и то же значение. Так же, как и в случае, когда рЬ==0. Но если pb указывает на В (и только), то у dynamic cast достаточно информации, чтобы вернуть О, а static cast должен поверить програ.ммисту, что pb указывает на D, и вернуть указатель на объект, предположительно принадлежащий классу D. Рассмотрим другой пример: class Dl : public D {/*...*/ }; class D2 : public В { /* ... */ }; class X : public Dl, public D2 {/*... */ }; void g() { D2* pd2 = new X; f(pd2); Здесь g () вызовет f {), передав объект в, который не является подобъектом D. Следовательно, dynamic cast правильно найдет соседний подобъект типа D, тогда как static cast вернет указатель на какой-то неподходящий подобъект X. Насколько я помню, первым, кто обратил мое внимание на этот феномен, был Мартин ОРиордан. 14.3.3. Оператор reinterpret cast Нотация reinterpret cast<T> (е) призвана заменить (Т) е для преобразований вида char* в int* и Some class* BUnrelated class*, которые внутренне небезопасны и зачастую зависят от реализации. При этом Some сlass и Unrelated class не связаны друг с другом отношением наследования. По сути дела, оператор reinterpret cast возвращает значение, получеппое в результате новой принудительной интерпретации своего аргумента. Например: class S; class Т; void f(int* pi, cliar* pc, S* ps, T* pt, int i) ( S* ps2 = reinterpret cast<S*>(pi); S* ps3 = reinterpret cast<S*>(pt); char* pc2 = reinterpret cast<char*>(pt); int* pi2 = reinterpret cast<int*>(pc); int 12 = reinterpret cast<int>(pc); int* pi3 = reinterpret cast<int*>(i); Оператор reinterpret cast позволяет преобразовать указатель в любой другой, а также любой интегральный тип в любой указательный тип и наоборот. Все эти операции небезопасны, зависят от реализации. Если только нужное преобразование не является по существу низкоуровневым и небезопасным, программисту лучще воспользоваться какими-то другими приведениями. В отличие от static cast, на результаты работы reinterpret cast нельзя полагаться ни в одном из случаев за исключением приведения к исходному типу. Все остальные применения в лучшем случае непереносимы. Вот почему преобразования между указателем на функцию и указателем на член относятся к reinterpret cast, а не к static cast. Например: void thump(char* р) { *р = х; } typedef void (*PF)(const char*); PF pf; void g(const char* pc) { thump(pc); ошибка: неправильный тип аргумента pf = &thump; ошибка pf = static cast<PF>(bthump); ошибка! pf = reinterpret cast<PF> (Scthump) ; допускается, HO за последствия отвечаете только вы pf(pc); правильная работа не гарантируется! Понятно, что присваивать pf указатель на thump опасно, так как это действие в обход системы типов. С помощью данной операции адрес константы может быть передан какой-то функции, которая сделает попытку ее модифицировать. Поэтому и нужно использовать приведение, причем именно оператор reinterpret cast. Но для многих неожиданно, что вызов thump через pf все равно не гарантирован (в С-и- так же, как и в С). Дело в том, что компилятору разрешено использовать разные соглашения о вызове для функций разных типов. В частности, есть веские основания для того, чтобы константные и неконстантные функции вызывались по-разному. Заметим, что reinterpret cast не осуществляет навигацию по иерархии классов. Например: class А {/*...*/ }; class В {/*...*/ }; class D : public А, public В {/*...*/ }; void f(B* pb) { D* pdl = reinterpret cast<D*>(pb); D* pd2 = static cast<D*>(pb); Здесь pdl и pd2, как правило, получат различные значения. При вызове f(new D); pd2 будет указывать на начало переданного объекта D, тогда как pdl - на начало подобъекта В в объекте D. Операция reinterpret cast <Т> {arg) почти так же неудачна, как и (Т) arg. Однако она более заметна в тексте программы, никогда не осуществляет навигации по классам и не отбрасывает атрибут const. Для данной операции есть альтернативы, reinterpret cast - средство для низкоуровневых преобразований, обычно зависящих от реализации, - и только! 14.3.4. Оператор const cast Труднее всего было найти замену для старых приведений типов, чтобы корректно обрабатывался атрибут const. В идеале нужно было гарантировать, что константность никогда не снимается сама собой. По этой причине каждый из операторов reinterpret cast, dynamic cast и static cast не изменял этот атрибут. Нотация const cast<T> (е) призвана заменить (Т) е для преобразований, которым нужно получить доступ к данны.м с модификатором const или volatile. Например: extern С char* strchr(char*, char); inline const char* strchr(const char* p, char c) { return strchr(const cast<char*>p, char c); В const cast<T> (e) тип аргумента Т должен совпадать с типом аргу.мента е во всем, кроме модификаторов const и volatile. Результато.м будет то же значение, что у е, только его типом станет т. Отметим, что результат отбрасывания const для объекта, который первоначально был объявлен с этим модификатором, не определен (см. раздел 13.3). 14.3.4.1. Проблемы защиты const В системе типов есть некоторые тонкости, которые открывают дыры в защите от неявного нарушения константности. Рассмотрим пример: const char сс = а; const char* pec = &сс; const char** ppcc = &pcc; void* pv = ppcc; никакого приведения не нужно: ppcc - не константный указатель, он лишь указывает на таковой, но константность исчезла! char** ррс - (char**)pv; указывает на рсс void f() { **ррс = х; теперь можем изменять константную переменную!
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |