|
Программирование >> Арифметические и логические операции
Зачем мне использовать встроенные функции? Почему не использовать просто #define макросы? Поскольку #define макросы опасны. В отличие от #define макросов, встроенные (inline) функции не подвержены известным ошибкам двойного вычисления, поскольку каждый аргумент встроенной функции вычисляется только один раз. Другими словами, вызов встроенной функции - это то же самое что и вызов обычной функции, только быстрее: Макрос, возвращающий модуль (абсолютное значение) i #define unsafe(i) \ ( (i) >= 0 ? (i) : ) Встроенная функция, возвращающая абсолютное значение i inline int safe(int i) return i >= 0 ? i : -i; int f(); void userCode(int x) int ans; ans = unsafe(x++); Ошибка! x инкрементируется дважды ans = unsafe(f()); Опасно! f() вызывается дважды ans = safe(x++); Верно! x инкрементируется один раз ans = safe(f()); Верно! f() вызывается один раз Также, в отличие от макросов, типы аргументов встроенных функций проверяются, и выполняются все необходимые преобразования. Макросы вредны для здоровья; не используйте их, если это не необходимо. Что такое ошибка в порядке статической инициализации ( static initialization order fiasco )? Незаметный и коварный способ убить ваш проект. Ошибка порядка статической инициализации - это очень тонкий и часто неверно воспринимаемый аспект С++. К сожалению, подобную ошибку очень сложно отловить, поскольку она происходит до вхождения в функцию main(). Представьте себе, что у вас есть два статических объекта x и y, которые находятся в двух разных исходных файлах, скажем x.cpp и y.cpp. И путь конструктор объекта у вызывает какой-либо метод объекта x. Вот и все. Так просто. Проблема в том, что у вас ровно пятидесятипроцентная возможность катастрофы. Если случится, что единица трансляции с x.cpp будет проинициализирована первой, то все в порядке. Если же первой будет проинициализирована единица трансляции файла y.cpp, тогда конструктор объекта у будет запущен до конструктора x, и вам крышка. Т.е., конструктор y вызовет метод объекта x, когда сам x еще не создан. Примечание: ошибки статической инициализации не распространяются на базовые/встроенные типы, такие как int или char*. Например, если вы создаете статическую переменную типа float, у вас не будет проблем с порядком инициализации. Проблема возникает только тогда, когда у вашего статического или глобального объекта есть конструктор. Как предотвратить ошибку в порядке статической инициализации? Используйте создание при первом использовании , то есть, поместите ваш статический объект в функцию. Представьте себе, что у нас есть два класса Fred и Barney. Есть глобальный объект типа Fred, с именем x, и глобальный объект типа Barney, с именем y. Конструктор Barney вызывает метод goBowling() объекта x. Файл x.cpp содержит определение объекта x: File x.cpp #include Fred.hpp Fred x; Файл y.cpp содержит определение объекта y: File y.cpp #include Barney.hpp Barney y; Для полноты представим, что конструктор Barney::Barney() выглядит следующим образом: File Barney.cpp #include Barney.hpp Barney::Barney() ... x.goBowling(); Проблема случается, если y создается раньше, чем x, что происходит в 50% случаев, поскольку x и y находятся в разных исходных файлах. Есть много решений для этой проблемы, но одно очень простое и переносимое - заменить глобальный объект Fred x, глобальной функцией x(), которая возвращает объект типа Fred по ссылке. File x.cpp #include Fred.hpp FredS x() static Fred* ans = new Fred(); return *ans; Поскольку локальные статические объекты создаются в момент, когда программа в процессе работы в первый раз проходит через точку их объявления, инструкция new Fred() в примере выше будет выполнена только один раз: во время первого вызова функции x(). Каждый последующий вызов возвратит тот же самый объект Fred (тот, на который указывает ans). И далее все случаи использования объекта x замените на вызовы функции x(): File Barney.cpp #include Barney.hpp Barney::Barney() x().goBowling(); Это и называется создание при первом использовании , глобальный объект Fred создается при первом обращении к нему. Отрицательным моментом этой техники является тот факт, что объект Fred нигде не уничтожается. Примечание: ошибки статической инициализации не распространяются на базовые/встроенные типы, такие как int или char*. Например, если вы создаете статическую переменную типа float, у вас не будет проблем с порядком инициализации. Проблема возникает только тогда, когда у вашего статического или глобального объекта есть конструктор. Как бороться с ошибками порядка статической инициализации объектов - членов класса? Предположим, у вас есть класс X, в котором есть статический объект Fred: File X.hpp class X { public: private: static Fred x ; Естественно, этот статический член инициализируется отдельно: File X.cpp #include X.hpp Fred X::x ; Опять же естественно, объект Fred будет использован в одном или нескольких методах класса X: void X::someMethod() x .goBowling(); Проблема проявится, если кто-то где-то каким-либо образом вызовет этот метод, до того как объект Fred будет создан. Например, если кто-то создает статический объект X и вызывает его someMethod() во время статической инициализации, то ваша судьба всецело находится в руках компилятора, который либо создаст X::x , до того как будет вызван someMethod(), либо же только после. В любом случае, всегда можно сохранить переносимость (и это абсолютно безопасный метод), заменив статический член X::x на статическую функцию-член: File X.hpp class X { public: ... private: static FredS x(); Естественно, этот статический член инициализируется отдельно: File X.cpp #include X.hpp Freds X::x() static Fred* ans = new Fred(); return *ans; После чего вы просто меняете все x на x(): void X::someMethod() { x().goBowling(); Если для вас крайне важна скорость работы программы и вас беспокоит необходимость дополнительного вызова функции для каждого вызова X::someMethod(), то вы можете сделать static Fred&. Как вы помните, статические локальные переменные инициализируются только один раз (при первом прохождении программы через их объявление), так что X::x() теперь будет вызвана только один раз: во время первого вызова X::someMethod(): void X::someMethod() static Fred& x = X::x(); x.goBowling(); Примечание: ошибки статической инициализации не распространяются на базовые/встроенные типы, такие как int или char*. Например, если вы создаете статическую переменную типа float, у вас не будет проблем с порядком инициализации. Проблема возникает только тогда, когда у вашего статического или глобального объекта есть конструктор. Как мне обработать ошибку, которая произошла в конструкторе? Сгенерируйте исключение. Что такое деструктор? Деструктор - это исполнение последней воли объекта. Деструкторы используются для высвобождения занятых объектом ресурсов. Например, класс Lock может заблокировать ресурс для эксклюзивного использования, а его деструктор этот ресурс освободить. Но самый частый случай - это когда в конструкторе используется new, а в деструкторе - delete. Деструктор это функция готовься к смерти . Часто слово деструктор сокращается до dtor. В каком порядке вызываются деструкторы для локальных объектов? В порядке обратном тому, в каком эти объекты создавались: первым создан - последним будет уничтожен. В следующем примере деструктор для объекта b будет вызван первым, а только затем деструктор для объекта a: void userCode() Fred a; Fred b; В каком порядке вызываются деструкторы для массивов объектов? В порядке обратном созданию: первым создан - последним будет уничтожен. В следующем примере порядок вызова деструкторов будет таким: a[9], a[8], a[1], a[0]: void userCode() Fred a[10]; Могу ли я перегрузить деструктор для своего класса? Нет. У каждого класса может быть только один деструктор. Для класса Fred он всегда будет называться Fred::~Fred(). В деструктор никогда не передаётся никаких параметров, и сам деструктор никогда ничего не возвращает. Всё равно вы не смогли бы указать параметры для деструктора, потому что вы никогда на вызываете деструктор напрямую (точнее, почти никогда). Могу ли я явно вызвать деструктор для локальной переменной? Нет! Деструктор всё равно будет вызван еще раз при достижении закрывающей фигурной скобки } конца блока, в котором была создана локальная переменная. Этот вызов гарантируется языком, и он происходит автоматически; нет способа этот вызов предотвратить. Но последствия повторного вызова деструктора для одного и того же объекта могут быть плачевными. Бах! И вы покойник...
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |