Программирование >>  Арифметические и логические операции 

1 ... 39 40 41 [ 42 ] 43 44 45 ... 53


Зачем мне использовать встроенные функции? Почему не использовать просто #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(). В деструктор никогда не передаётся никаких параметров, и сам деструктор никогда ничего не возвращает.

Всё равно вы не смогли бы указать параметры для деструктора, потому что вы никогда на вызываете деструктор напрямую (точнее, почти никогда).

Могу ли я явно вызвать деструктор для локальной переменной?

Нет!

Деструктор всё равно будет вызван еще раз при достижении закрывающей фигурной скобки } конца блока, в котором была создана локальная переменная. Этот вызов гарантируется языком, и он происходит автоматически; нет способа этот вызов предотвратить. Но последствия повторного вызова деструктора для одного и того же объекта могут быть плачевными. Бах! И вы покойник...



1 ... 39 40 41 [ 42 ] 43 44 45 ... 53

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