Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 [ 62 ] 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

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

1(6). С++ имеет собственные средства для работы с именами. То, что макросы предоставляют другой способ для выполнения той же работы, приводит к эффекту, который лучше всего определить как "нездоровое взаимодействие".

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

Именно это, увы, и происходит в случае с нашей функцией sleep. Вся причина того, что разработчик библиотеки любезно предоставил макрос для автоматического переименования Sleep в sleepFx заключается в наличии обоих функций в поставляемой библиотеке. Рассмотрим случай, когда эти функции могут иметь различные сигнатуры. Когда мы пишем собственную функцию Si eep, мы знаем о наличии библиотечной функции Sleep и можем принять меры для того, чтобы избежать неоднозначностей или других ошибок, которые могут привести к проблемам. Более того, мы можем умышленно использовать перефузку, чтобы получить функцию с поведением, похожим на поведение библиотечной функции sleep. Но если при этом окажется, что имя функции совсем иное, то перегрузка не только поведет себя не так, как ожидалось, но ее может не быть вообще.

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

Если бы даже на этом наш рассказ о последствиях применения макросов и закончился, рассказанное было бы достаточно неприятно. Но, к сожалению, осколки от взрыва макроса разлетаются в разных направлениях...

2. Макросы не заботятся о типах.

Исходное предназначение макроса sleep, о котором шла речь выше, - изменение имени глобальной функции. К сожалению, макрос изменяет имя Sleep везде, где только находит его. Если ему попадется глобальная переменная с именем sleep, то это имя будет молча заменено на SleepEX. Это еще одна очень Плохая Вещь.

3. Макросы не заботятся об области видимости.

Еще хуже то, что макрос, созданный для замены имени глобальной функции, не являющейся членом класса, будет с тем же успехом заменять как имена функций, так и другие такие же имена, являющиеся членами класса или инкапсулированные в ваше собственное пространство имен. В рассматриваемом в задаче случае у нас имеется класс, в котором есть функции Si eep и SleepEx; многие из описанных в условии задачи проблем могут быть, по крайней мере, частично объяснены наличием макроса, п е ре и мс н о вьшаю ще го sleep, что приводит к невидимой перегрузке наших собственных функций друг с другом. Такая перегрузка функций-членов (как и глобальная, о которой рассказывалось в п. 1) может объяснить, почему иногда вызывается не та



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

Если вы решите, что это еше одна Плохая Вещь - вы окажетесь совершенно правы. Больше всего это напоминает доктора без перчаток и диплома (неразумный разработчик заголовочного файла библиотеки) с грязными руками (макросами), который вскрывает брюшную полость пациента (класс или пространство имен) и начинает там копаться, переставляя местами внутренности (члены и прочий код)... во время приступа лунатизма (даже не осознавая, что же он делает).

Резюме

Если говорить коротко - макросы никогда не заботятся ни о чем.

> Рекомендация

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

Ваша стандартная реакция на макрос должна быть однозначной - "Изыди!" (или, на худой конец, "Vadc retro!") - пока вы полностью не уясните себе причины его использования в конкретном случае, где его применение не является "хаком". Макросы небезопасны с точки зрения типов, с точки зрения областей видимости... да они просто небезопасны и точка! Если вы вынуждены писать макрос - избегайте размещения его в заголовочном файле и попытайтесь дать ему длинное и персонифицированное имя, которое вряд ли кому-нибудь придет в голову использовать в качестве имени в программе (и вообще вряд ли придет в голову кому-либо).

> Рекомендация

Для инкапсуляции имен предпочтительно использовать пространства имен.

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



Задача 32. Небольшие очепятки

и прочие курьезы Сложность: 5

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

Попытайтесь ответить на вопросы, и с прибегая к помоши компилятора. Вопрос для профессионала

1. Каким будет вывод следующей программы, скомпилированной соответствующим сгандаргу компилятором?

пример 32-1

#1 nclude <iostream> #include <iomanip>

int mainO { int X = 1;

for( int i = 0; i < 100; ++i );

Следующая строка - инкремент???????????/

++x;

std::cout « x « std::endl;

2. О скольких различных ошибках сообщит соответствующий стандарту компилятор при работе с приведенным исходным текстом?

пример 32-2

struct X {

stati с bool f( int* p ) {

return p && 0[p] and not p[l:»p[2];

Решение

1. Каким будет вывод следующей программы, скомпилированной соответствующим стандарту компилятором?

Вывод примера 32-1, если предположить, что в конце строки с комментарием нет невидимых пробельных символов, будет представлять собой 1. Здесь есть две хитрости - одна очевидная и одна более тонкая. Начнем с цикла for.

for С int i =0; i < 100; ++i );

Наличие точки с запятой в конце - стандартная опечатка, которая (обычно непредумышленно) делает тело цикла for состоящим из пустой инструкции. Несмотря на отступы (и даже охватывающие фигурные скобки) следующие за таким циклом строки телом цикла не являются.

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

Давайте внимательно рассмотрим строку с комментарием. Вы заметили, как странно она заканчивается - символом /?



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 [ 62 ] 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85