|
Программирование >> Оптимизация возвращаемого значения
Оператор reinterpret cast обычно применяется для приведения указателей на функции. Предположим, например, что у вас есть массив указателей на функции определенного типа: typedef void {*FuncPtr) {) ; FuncPtr - это указатель на / / функцию, которая не имеет / / аргументов и возвращает значениеvoid. FuncPtr funcPtrArray [10] ; funcPtrArray - это массив из 10 указателей FuncPtr. Предположим, что вы хотите по какой-либо причине поместить в funcPtrArray указатель на следующую функцию: int doSomething{) ; Вы не можете сделать это без приведения типа, потому что функция doSomething имеет тип, не соответствующий типу элементов массива funcPtrArray. Функции в funcPtrArray возвращают значение типа void, а функция do-Something возвращает значение int: funcPtrArray[0] =&doSomething; Ошибка! типы не совпадают. Оператор reinterperet cast позволяет подчинить работу компиляторов вашему замыслу: funcPtrArray[0] = Компилируется нормально. reinterpret cast<FuncPtr> (ScdoSomething) ; Операция преобразования указателей на функции не является переносимой (язык C-I-I- не гарантирует, что все указатели на функции хранятся единообразно), и в некоторых случаях такая операция дает неправильный результат (см. правило 31). Из-за этого использовать указатели на функции следует только в самом крайнем случае. Если ваши компиляторы не поддерживают новые виды операторов приведения типа, вместо static cast, const cast и reinterpret cast допускается использовать традиционное приведение типа. Более того, придать синтаксису их использования сходство с новым синтаксисом можно с помощью макроопределений: #definestatic cast(TYPE, EXPR) {(TYPE) (EXPR)) #defineconst cast(TYPE, EXPR) ((TYPE) (EXPR)) #definereinterpret cast(TYPE, EXPR) ((TYPE) (EXPR)) Эти макроопределения допускается использовать следующим образом: double result = static cast(double, firstNumber)/secondNumber; update{const cast(SpecialWidget*, &sw) ) ; funcPtrArray[0] =reinterpret cast(FuncPtr, &doSomething); Включение в программу макроопределений не столь безопасно, как использование настоящих операторов, но они облегчат модификацию вашего кода, когда компиляторы, с которыми вы работаете, начнут поддерживать новые операторы. Не существует простого способа эмулировать оператор dynamic cast, но многие библиотеки содержат функции, выполняющие безопасные операции приведения типов, использующих наследование. Если такие функции отсутствуют, а вам просто необходимо выполнить операцию приведения типа, то и в этом случае можно обратиться к стилю языка С, но jTHTe, что вы не сумеете обнаружить безуспешное приведение типов. Разумеется, в такой ситуации также допускается создать макроопределение, похожее на оператор dynamic cast: #definedynamic cast(TYPE,EXPR) ((TYPE) (EXPR)) Помните, что это лишь приблизительный аналог, который не выполняет все функции dynamic cast: нельзя обнаружить безуспешное преобразование типов. Как вы, наверное, заметили, новые операторы приведения типов выглядят непривычно и их сложно вводить с клавиатуры. Если вы находите их вид неприглядным, возможно, вас утешит информация о том, что в С++ работает и приведение типов в стиле языка С. Однако, теряя в красоте, новые операторы делают приведение типов более ясным и распознаваемым. Программы, их использующие, проще разбирать (как человеку, так и программным инструментам). Новые операторы позволяют компиляторам находить ошибки приведения типов, которые в противном случае остались бы не выявленными. Согласитесь, это серьезные аргументы в пользу того, чтобы не использовать преобразование типов в стиле языка С. Правило 3. Никогда не используйте полиморфизм в массивах Одна из наиболее важных черт наследования состоит в том, что, используя указатели и ссылки на объекты производного класса, вы можете пол5Д1Ить доступ к объектам базового класса. Такое поведение указателей и ссылок называют полиморфизмом (polymorphically), - они ведут себя так, как будто принадлежат нескольким типам. Язык С++ теоретически позволяет управлять массивами из объектов производного класса через указатели и ссылки на объекты базового класса, но на практике это почти всегда работает не так, как хотелось бы. Представим, например, что у вас есть класс BST (для объектов двоичного дерева поиска) и второй класс, BalancedBST, наследующий от BST: classBST { ... }; classBalancedBST:publicBST {...} ; В реальной программе такие классы реализуются через шаблоны, но здесь это не существенно, а синтаксис шаблонов только затрудняет чтение. Чтобы не усложнять пример, предположим, что BST и BalancedBST содержат только переменные типа int. Рассмотрим функцию, распечатывающую содержимое каждого объекта BST в массиве элементов BST: voidprintBSTArray (ostream&s, const BST array [] , int numEl ement s) printBSTArray(cout, BSTArray,10); Работает нормально. Однако посмотрите, что происходит, когда вы передаете функции print-BSTArray массив объектов типа BalancedBST: balancedBSTbBSTArray[10]; printBSTArray(cout, bBSTArray,10); Нормально работает? Ваши компиляторы без проблем обработают вызов этой функции, но взгляните на цикл, для которого он должен сгенерировать код: for (int i = О; i < numElements; ++i) { s<<array [i] ; Выражение array [ i ] на самом деле является сокращением для арифметического выражения, вычисляющего указатель, оно эквивалентно выражению * (array + i). Мы знаем, что array - это указатель на начало массива, но как далеко от ячейки памяти, на которую указывает array, находится память, на которую указывает выражение array + i? Расстояние между ними определяется выражением i * sizeof (элемент массива), потому что между array [0] и array [ i ] как раз i элементов. Для того чтобы генерировать код, обеспечивающий корректный доступ к элементам массива, компиляторы должны знать размер каждого элемента массива. Это легко. Переменная array описана как массив элементов типа BST, так что расстояние между указателями array и array + i равно i * sizeof(BST). По крайней мере, так это выглядит с точки зрения компиляторов. Но если вы передали функции printBSTArray в качестве аргумента массив объектов типа BalancedBST, то компиляторы скорее всего ошибаются. В этом сл5ае они полагают, что размер элемента массива равен размеру объекта BST, в действительности же размер элемента равен размеру объекта BalancedBST. Обычно производные классы имеют большее количество членов класса, чем базовый класс, поэтому они, как правило, превосходят базовый класс по размеру. Таким образом, следует ожидать, что объект типа BalancedBST будет больше, чем объект типа EST. Если это так, то выражение для вычисления указателей, созданное в теле функции printBSTArray, будет неверным для массивов элементов типа BalancedBST, и нельзя предсказать, что произойдет при вызове функции printBSTArray, for (int i = 0; i <numElements; + + i) { s << array[i]; Предполагается, что оператор << / / определен для объектов BST. Этот код нормально работает, когда в качестве аргумента функции передается массив объектов BST: BSTBSTArray[10] ;
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |