Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 104 105 106 [ 107 ] 108 109 110 ... 144


Поскольку объем книги ограничен, здесь рассматривается только самая большая трудность - указатели. Проработка остальных вопросов: арифметических типов, указателей на члены, ссылок и т.д. - оставлена как упражнение читателю.

14.3.1. Недостатки старых приведений типов

Выражение {Т) ехрг даст (за очень немногими исключениями) значение типа Т, тем или иным способом полученное на основе значения ехрг. Допускаю, что для этого потребуется иная интерпретация битов ехрг, могут также произойти сужение или расширение диапазона значений, арифметические операции над адресами для навигации по иерархии классов, отключение атрибута const или vol(. : ile и др. Видя перед собой изолированное выражение с приведением типа, пользователь не в состоянии определить, что имел в виду его автор. Например:

const X* pc = new X; ...

pv = (Y*) pc;

Хотел ли программист получить указатель на тип, никак не связанный с X? Или убрать атрибут const? Или то и другое одновременно? Может быть, он намеревался получить доступ к классу Y, базовому для X?

Более того, безобидное, на первый взгляд, изменение объявления способно без всяких предупреждений полностью изменить смысл выражения, например:

class X : public А, public В { /* ... */ };

void f(X* рх) {

((В*)рх)->g(); вызывается g из класса В рх->В:-.д(); более явный и, значит, лучший способ

Изменим определение класса X так, чтобы в больше не являлся для него базовым классом, и смысл (В*) рх станет совершенно другим, а компилятор не сможет обнаружить ошибку.

Помимо связанных со старыми приведениями семантических проблем, неудобна и нотация. Она близка к минимальной и использует только скобки, то есть синтаксическую конструкцию, которая в С и так употребляется чрезмерно часто. Поэтому пользователю трудно найти в программе все приведения. И с по.мошью инструментального средства типа grep осуществлять поиск нелегко. К тому же синтаксис приведения типов - одна из основных причин усложнения грамматического разбора программы на С-и-.

Итак, старые приведения типов:

□ трудны для понимания, поскольку предоставляют одну и ту же нотацию для различных слабо связанных между собой операций;

□ провоцируют ошибки, так как почти любая комбинация типов имеет какую-то допустимую интерпретацию;

□ с трудом отыскиваются в исходном тексте как вручную, так и с помощью простых утилит;

□ усложняют грамматику С и C-t-t-.



Новые операторы приведения призваны распределить функциональность старых по разным категориям. Они должны уметь выполнять все те же операции, что и прежние операторы, иначе будут приведены доводы в пользу использования старых и в дальнейшем. Было найдено только одно исключение: с помошью старого синтаксиса .можно приводить от производного класса к его закрыто.му базовому классу. Я не вижу оснований для такой операции - она опасна и бесполезна. Невозможно получить доступ к полностью закрыто.му представлению объекта, да это и не нужно. Тот факт, что старые приведения типов позволяли добраться до части представления закрытого класса, - досадная случайность. Например:

class D : public А, private В { private:

int m;

. . .

void f(D* pd) f0 - не член и не дружественная функция D {

В* pbl = (B*)pd; получаем доступ к закрытому базовому

классу В. Не годится! 3* рЬ2 = static cast<B*>(pd); ошибка: нет доступа к закрытому

классу. Верно!

Если исключить манипуляции с pd как с указателем на неинициализированную память, то функция f () не получит доступ к D: :т. Таким образом, новые операторы принедения закрывают дыру в правилах доступа и обеспечивают большую логическую непротиворечивость.

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

14.3.2. Оператор static cast

Нотация static cast<T> (е) призвана заменить (Т) е для преобразований от Base* (указатель на базовый класс) к Derived* (указатель на производный класс). Такие преобразования не всегда безопасны, но часто встречаются и могут разумно определяться даже в отсутствие проверок во время исполнения. Например:

class В (/*...*/ };

class D : public В {/*...*/ };

void f(B* pb, D* pd)



D* pd2 = static cast<D*>(pb); раньше писали (D*)pb

В* рЬ2 = static cast<B*>(pb); безопасное преобразование . . .

Можно представлять себе static cast как явное обращение операции неявного преобразования типов. Если на время забыть о том, что static cast сохраняет константность, то он позволяет выполнить S->T при условии, что преобразование T->S может быть выполнено неявно. Отсюда следует, что в больщинстве случаев результат stat ic cast можно использовать без дальнейшего приведения. В этом отношении он отличается от reinterpret cast (см. раздел 14.3.3).

Кроме того, static cast вызывает преобразования, которые можно выполнить неявно (например, стандартные и определенные пользователем).

В противоположность dynamic cast для применения static cast крЬ не требуется никаких проверок во время исполнения. Объект, на который указывает pb, может и не принадлежать к классу D, тогда результаты использования *pd2 не определены и, возможно, пагубны.

В отличие от старых приведений типов указательные и ссылочные типы должны быть полными. Это означает, что попытка использовать static cast для преобразования к указателю на тип или от него в случае, когда компилятор еще не встречал объявления этого типа, приведет к ошибке. Например:

class X; X - неполный тип class Y; Y - неполный тип

void f(X* рх) {

Y* р = (Y*)px; разрешено, но опасно

р = static cast<Y*>(рх); ошибка: X и Y не определены

Тем самым устраняется еще один источник ошибок. Если вам нужно приведение к неполным типа.м, пользуйтесь оператором reinterpret cast (см. раздел 14.3.3), чтобы ясно показать, что вы не пытаетесь осуществлять навигацию по иерархии, или оператором dynamic. cast (см. раздел 14.2.2).

14.3.2.1. Статические и динамические приведения

Применение static cast и dynamic cast к указателям на классы приводит к навигации по иерархии классов. Однако static cast опирается только на статическую инфор.мацию и поэтому может работать некорректно. Рассмотрим пример:

class В {/*...*/ }:

class D : public В {/*...*/ };

void f(B* pb)



1 ... 104 105 106 [ 107 ] 108 109 110 ... 144

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика