Короткий faq по c

[10.11] Що таке помилка в порядку статичної ініціалізації ( "static initialization order fiasco")?

Непомітний і підступний спосіб убити ваш проект.

Помилка порядку статичної ініціалізації - це дуже тонкий і часто невірно сприймається аспект С ++. На жаль, подібну помилку дуже складно відловити, оскільки вона відбувається до входження в функцію main ().

Уявіть собі, що у вас є два статичних об'єкта x і y. які знаходяться в двох різних вихідних файлах, скажімо x.cpp і y.cpp. І шлях конструктор об'єкта y викликає якийсь метод об'єкта x.

От і все. Так просто.

Проблема в тому, що у вас рівно п'ятдесятивідсотковою можливість катастрофи. Якщо трапиться, що одиниця трансляції з x.cpp буде проініціалізувати першої, то все в порядку. Якщо ж першою буде проініціалізувати одиниця трансляції файлу y.cpp, тоді конструктор об'єкта y буде запущений до конструктора x. і вам кришка. Тобто конструктор y викличе метод об'єкта x. коли сам x ще не створений.

Ідіть працювати в МакДональдс. Робіть Біг-Маки, забудьте про класи.

Примітка: помилки статичної ініціалізації не поширюються на базові / вбудовані типи, такі як int або char *. Наприклад, якщо ви створюєте статичну змінну типу float. у вас не буде проблем з порядком ініціалізації. Проблема виникає лише тоді, коли у вашого статичного або глобального об'єкта є конструктор.

[10.12] Як запобігти помилці в порядку статичної ініціалізації?

Використовуйте "створення при першому використанні", тобто, помістіть ваш статичний об'єкт в функцію.

Уявіть собі, що у нас є два класи Fred і Barney. Є глобальний об'єкт типу Fred. з ім'ям x. і глобальний об'єкт типу Barney, з іменемy. Конструктор Barney викликає метод goBowling () об'єкта x. Файл x.cpp містить визначення об'єкта x.

Файл y.cpp містить визначення об'єкта y.

Для повноти уявімо, що конструктор Barney :: Barney () виглядає наступним чином:

Як описано вище [10.11], проблема трапляється, якщо y створюється раніше, ніж x. що відбувається в 50% випадків, оскільки x іy знаходяться в різних вихідних файлах.

Є багато рішень для цієї проблеми, але одне дуже просте і переносний - замінити глобальний об'єкт Fredx. глобальної функцією x (). яка повертає об'єкт типу Fred за посиланням.

Це і називається "створення при першому використанні", глобальний об'єкт Fred створюється при першому зверненні до нього.

Негативним моментом цієї техніки є той факт, що об'єкт Fred ніде не знищується. Книга C ++ FAQ Book описує додаткову техніку, яка дозволяє вирішити і цю проблему (правда, ціною появи можливий помилок порядку статичної деініціалізацію).

Примітка: помилки статичної ініціалізації не поширюються на базові / вбудовані типи, такі як int або char *. Наприклад, якщо ви створюєте статичну змінну типу float. у вас не буде проблем з порядком ініціалізації. Проблема виникає лише тоді, коли у вашого статичного або глобального об'єкта є конструктор.

[10.13] Як боротися з помилками порядку статичної ініціалізації об'єктів - членів класу?

Використовуйте ту ж саму техніку, яка описана в [10.12], але замість глобальної функції використовуйте статичну функцію-член.

Припустимо, у вас є клас X. в якому є статичний об'єкт Fred.

Природно, цей статичний член ініціалізується окремо:

Знову ж природно, об'єкт Fred буде використаний в одному або декількох методах класу X.

Проблема проявиться, якщо хтось десь якимось чином викличе цей метод, до того як об'єкт Fred буде створений. Наприклад, якщо хтось створює статичний об'єкт X і викликає його someMethod () під час статичної ініціалізації, то ваша доля цілком знаходиться в руках компілятора, який або створить X :: x_. до того як буде викликаний someMethod (). або ж тільки після.

(Мушу зауважити, що ANSI / ISO комітет по C ++ працює над цією проблемою, але компілятори, які працюють відповідно до останніх змін, поки недоступні, можливо, в майбутньому в цьому розділі будуть зроблені доповнення в зв'язку із зміною ситуації.)

У будь-якому випадку, завжди можна зберегти переносимість (і це абсолютно безпечний метод), замінивши статичний член X :: x_ на статичну функцію-член:

Природно, цей статичний член ініціалізується окремо:

Після чого ви просто міняєте все x_ на x ().

Примітка: помилки статичної ініціалізації не поширюються на базові / вбудовані типи, такі як int або char *. Наприклад, якщо ви створюєте статичну змінну типу float. у вас не буде проблем з порядком ініціалізації. Проблема виникає лише тоді, коли у вашого статичного або глобального об'єкта є конструктор.

[10.14] Як мені обробити помилку, яка сталася в конструкторі?

Згенеруйте виняток. Дивіться подробиці в [17.1].

Розділ [11]: Деструктори

[11.1] Що таке деструктор?

Деструкція - це виконання останньої волі об'єкта.

Деструктори використовуються для вивільнення зайнятих об'єктом ресурсів. Наприклад, клас Lock може заблокувати ресурс для ексклюзивного використання, а його деструктор цей ресурс звільнити. Але найчастіший випадок - це коли в конструкторі використовується new. а в деструкції - delete.

Деструкція це функція "готуйся до смерті". Часто слово деструктор скорочується до dtor.

[11.2] У якому порядку викликаються деструктори для локальних об'єктів?

В порядку зворотному тому, в якому ці об'єкти створювалися: першим створений - останнім буде знищений.

У наступному прикладі деструктор для об'єкта b буде викликаний першим, а тільки потім деструкція для об'єкта a:

[11.3] У якому порядку викликаються деструктори для масивів об'єктів?

В порядку зворотному створення: першим створений - останнім буде знищений.

У наступному прикладі порядок виклику деструкторів буде таким: a [9], a [8]. a [1], a [0]:

[11.4] Чи можу я перевантажити деструктор для свого класу?

У кожного класу може бути тільки один деструктор. Для класу Fred він завжди буде називатися Fred ::

Fred (). У деструктор ніколи не передається ніяких параметрів, і сам деструктор ніколи нічого не повертає.

Все одно ви не змогли б вказати параметри для деструктора, тому що ви ніколи на викликаєте деструктор безпосередньо [11.5] (точніше, майже ніколи [11.10]).

[11.5] Чи можу я явно викликати деструктор для локальної змінної?

Деструкція все одно буде викликаний ще раз при досягненні закриває фігурної дужки> кінця блоку, в якому була створена локальна змінна. Цей виклик гарантується мовою, і він відбувається автоматично; немає способу цей виклик запобігти. Але наслідки повторного виклику деструктора для одного і того ж об'єкта можуть бути плачевними. Бах! І ви небіжчик.

[11.6] А що якщо я хочу, щоб локальна змінна "померла" раніше закриває фігурної дужки? Чи можу я при крайній необхідності викликати деструктор для локальної змінної?

Ні! [Дивіться відповідь на попереднє запитання [11.5]].

Припустимо, що (бажаний) побічний ефект від виклику деструктора для локального об'єкта File полягає в закритті файлу. І припустимо, що у нас є екземпляр f класу File і ми хочемо, щоб файл f був закритий раніше кінця свого області видимості (тобто раніше>):

Для цієї проблеми є просте рішення, яке ми покажемо в [11.7]. Але поки запам'ятайте тільки наступне: не можна явно викликати деструктор [11.5].

[11.7] Добре, я не буду явно викликати деструктор. Але як мені впоратися з цією проблемою?

[Також дивіться відповідь на попереднє запитання [11.6]].

Просто помістіть вашу локальну змінну в окремий блок. відповідний необхідному часу життя цієї змінної:

[11.8] А що робити, якщо я не можу розмістити змінну в окремий блок?

У більшості випадків ви можете скористатися додатковим блоком для обмеження часу життя вашої змінної [11.7]. Але якщо з якоїсь причини ви не можете додати блок, додайте функцію-член, яка буде виконувати ті ж дії, що і деструкція. Але пам'ятайте: ви не можете самі викликати деструктор!

Наприклад, у випадку з класом File. ви можете додати метод close (). Звичайний деструкція буде викликати close (). Зверніть увагу, що метод close () повинен буде якось відзначати об'єкт File. з тим щоб наступні дзвінки, крім намагалися закрити вже закритий файл. Наприклад, можна встановлювати змінну-член fileHandle_ в яке-небудь неиспользуемое значення, типу -1, і перевіряти на початку, чи не містить fileHandle_ значення -1.

Зверніть увагу, що іншим методам класу File теж може знадобитися перевіряти, чи не встановлений fileHandle_ в -1 (тобто чи не закритий файл).

Також зверніть увагу, що всі конструктори, які не відчиняють файл, повинні встановлювати fileHandle_ в -1.

[11.9] А чи можу я явно викликати деструктор для об'єкта, створеного за допомогою new?

Скоріш за все ні.

За винятком того випадку, коли ви використовували синтаксис розміщення для оператора new [11.10], вам слід просто видаляти об'єкти за допомогою delete. а не викликати явно деструктор. Припустимо, що ви створили об'єкт за допомогою звичайного new:

В такому випадку деструктор Fred ::

Fred () буде автомагіческі викликаний, коли ви видаляєте об'єкт:

Вам не слід явно викликати деструктор, оскільки цим ви не звільняєте пам'ять, виділену для об'єкта Fred. Пам'ятайте: delete p робить відразу дві речі [16.8]: викликає деструктор і звільняє пам'ять.

[11.10] Що таке "синтаксис розміщення" new ( "placement new") і навіщо він потрібен?

Є багато випадків для використання синтаксису розміщення для new. Найпростіше - ви можете використовувати синтаксис розміщення для приміщення об'єкта в певне місце в пам'яті. Для цього ви вказуєте місце, передаючи покажчик на нього в оператор new.

У рядку # 1 створюється масив з sizeof (Fred) байт, розмір якого достатній для зберігання об'єкта Fred. У рядку # 2 створюється покажчик place. який вказує на перший байт масиву (досвідчені програмісти на С напевно помітять, що можна було і не створювати цей покажчик; ми це зробили лише щоб код був більш зрозумілим [As if - :) YM]). У рядку # 3 фактично відбувається тільки виклик конструктора Fred :: Fred (). Покажчик this в конструкторі Fred буде дорівнює вказівником place. Таким чином, повертається покажчик теж буде дорівнює place.

НЕБЕЗПЕЧНО: Використовуючи синтаксис розміщення new ви берете на себе всю відповідальність за те, що передається вами покажчик вказує на достатній для зберігання об'єкта ділянку пам'яті з тим вирівнюванням (alignment), яке необхідно для вашого об'єкта. Ні компілятор, ні бібліотека не перевірятимуть коректність ваших дій в цьому випадку. Якщо ваш клас Fred повинен бути вирівняний четирёхбайтовой кордоні, але ви передали в new покажчик на НЕ вирівняний ділянку пам'яті, у вас можуть бути великі неприємності (якщо ви не знаєте, що таке "вирівнювання" (alignment), будь ласка, не використовуйте синтаксис розміщення new ). Ми вас попередили.

Також на вас лягає вся відповідальність за знищення розміщеного об'єкта. Для цього вам необхідно явно викликати деструктор:

Це практично єдиний випадок, коли вам потрібно явно викликати деструктор.

[11.11] Коли я пишу деструктор, чи повинен я явно викликати деструктори для об'єктів-членів мого класу?

Ні. Ніколи не треба явно викликати деструктор (за винятком випадку з синтаксисом розміщення new [11.10]).

[11.12] Коли я пишу деструктор похідного класу, чи потрібно мені явно викликати деструктор предка?

Ні. Ніколи не треба явно викликати деструктор (за винятком випадку з синтаксисом розміщення new [11.10]).

Деструкція похідного класу (неявний, створений компілятором, або явно описаний вами) автоматично викликає деструктори предків. Предки знищуються після знищення об'єктів-членів похідного класу. У разі множинного успадкування безпосередні предки класу знищуються в порядку зворотному порядку їх появи в списку успадкування.

Примітка: в разі віртуального успадкування порядок знищення класів складніше. Якщо ви покладаєтеся на порядок знищення класів в разі віртуального успадкування, вам знадобиться більше інформації, ніж містить цей FAQ.

Схожі статті