Безпека виняткових ситуацій

Гарантії безпеки виключних ситуацій (виняток- калька з російської не рекомендований до вживання) це набір договірних принципів програмування для реалізації клієнтських бібліотек, які були сформульовані Девідом Абрахамсом[en][1][2], стосовно міркувань про безпеку обробки виключних ситуацій в мовах програмування, які використовують механізм виключних ситуацій, зокрема C++.

Зазвичай, безпека виключних ситуацій бібліотеки чи компоненту означає, що він матиме адекватну поведінку коли трапляється виключна ситуація під час виконання. Для більшості людей, «адекватна» поведінка полягатиме в тому, що для всіх очікуваних ситуацій обробки виключних ситуацій: не має відбуватися витоку ресурсів, і програма має залишатися в чітко визначеному стані, так що її виконання може продовжуватись далі. Для більшості компонентів також очікується, що в результаті виникнення помилки, про неї буде повідомлено клієнту.

Рівні безпеки ред.

Існує декілька рівнів безпеки виключних ситуацій (в неспадному порядку безпечності):[3]

  1. Гарантія відсутності виключних ситуацій, або прозорість неполадок: Гарантовано, що операції завершуються вдало і задовільняють всі вимоги навіть під час виключних ситуацій. Якщо виникають виключні ситуації, вони будуть оброблятись всередині і не будуть видні клієнтам.
  2. Посилена безпека, або семантика комітів або відкатів: Операції можуть зазнати невдачі, але невдалі операції не мають завдавати шкоди, так що всі дані збережуть свої первинні значення.[4]
  3. Базова, або з гарантією відсутності витоків: Часткове виконання або невдалі операції можуть мати побічні ефекти, але всі інваріанти зберігаються і немає витоків ресурсів. Будь-які збережені дані будуть мати правильні значення, навіть якщо вони відрізнятимуться від тих, що були перед виникненням виключної ситуації.
  4. Відсутність безпеки виключних ситуацій: Немає ніяких гарантій.

Зазвичай, на базовому рівні безпеки виключних ситуацій необхідно писати надійний код. Більш високі рівні безпеки іноді може бути важко досягти, і можуть бути затратними за рахунок додаткового копіювання.

Техніки виконання безпеки виключних ситуацій ред.

Основними засобами мови програмування для написання безпечного коду є:

Основною ідеєю техніки/шаблону проектування «Resource Acquisition Is Initialization» (RAII) є те, що володіння ресурсом віддається об'єкту з обмеженою областю видимості. Зазвичай такий об'єкт створює (відкриває, виділяє пам'ять, і так далі) ресурс в своєму конструкторі. Таким чином, деструктор об'єкта може звільнити ресурс в кінці свого життя незалежно від того, що призвело до закінчення його життєвого циклу, вихід за межі видимості чи виключна ситуація.[5]

Міфи і забобони ред.

«Взаємодія між шаблонами і виключними ситуаціями (exceptions) погано зрозуміла.» Цей міф легко спростовується тим, що ніякої взаємодії не існує. Шаблон, після створення його екземпляру, в усіх відносинах працює як звичайний клас чи функція. Для того, щоб передбачити поведінку шаблона при виключних ситуаціях, слід думати про нього як про конкретний екземпляр, який даний шаблон реалізує. Врешті решт, узагальненість шаблонів не має викликати особливих занепокоєнь. Хоча клієнтська частина програми поставляє компоненту частину реалізації (яка може, якщо не прописана інша поведінка, генерувати довільні виключні ситуації), те ж саме виникає і при використанні віртуальних функцій або просто роботі із вказівниками на функції.

«Неможливо написати безпечний узагальнений контейнер». Це твердження часто виникає з посиланням на статтю Тома Каргіла (Tom Cargill)[6], в якій він досліджує проблему безпеки виключних ситуацій для узагальнених стекових шаблонів. В своїй статті, він підіймає багато корисних питань, але на жаль не може дати вирішення його проблеми. Тому він робить припущення, що рішення не існує. Але пісня публікації статті, послідувало багато прикладів безпечних узагальнених компонентів, серед яких контейнери стандартної бібліотеки C++.

«Наявність коду з генеруванням і обробкою виключних ситуацій сповільнює програму, а шаблони використовуються спеціально для отримання найкращої можливої продуктивності.» Хороша реалізація C++ не призведе до жодного циклу команд для обробки виключних ситуацій, до моменту їх генерування, а при обробці швидкість виконання коду буде порівняна з викликом функції. Програма, яка містить код обробки виключних ситуацій буде мати таку саму швидкодію, як і програма яка ігнорує можливість виникнення помилок. Використання виключних ситуацій (exceptions) може привести до прискорення програми, в порівнянні з «традиційним» способом обробки помилок з ряду причин. По-перше, блок catch явно вказує компілятору, який код відноситься до ситуацій обробки помилок; і він може виноситись окремо із звичайного ходу виконання програми, підвищуючи компактність посилань. По-друге, код із «традиційним» способом обробки помилок має зазвичай має повертати коди помилок, а код який їх викликає повинен постійно їх перевіряти після кожного виклику такої функції; використання виключень повністю позбавляє від цих накладних витрат.

«Виключення ускладнюють розуміння поведінки програми.» Зазвичай це приводять на підтримку міфу про «приховані» шляхи виконання програми, які відбувається під час знищення стеку (stack-unwinding). Приховані шляхи виконання програми не нове поняття для програміста C++, який завжди очікує, що локальні (автоматичні) змінні підлягають знищенню після виходу із функції:

ErrorCode f( int& result )         // 1 
{                                  // 2 
    X x;                           // 3 
    ErrorCode err = x.g( result ); // 4 
    if ( err != kNoError )         // 5 
        return err;                // 6 
    // ...Будь-який інший код слідує тут... 
    return kNoError;               // 7 
}

У прикладі наведеному вище, відбувається «прихований» виклик деструктору X::~X() у 6-ій і 7-ій строчці коду. Завдяки, використанню виключень, немає явного коду, який присвячений обробці помилки:

int f()                 // 1 
{                       // 2 
    X x;                // 3 
    int result = x.g(); // 4 
    // ...Будь-який інший код слідує тут...  
    return result;      // 5 
}

Для більшості програмістів, які знайомі з механізмом обробки виключних ситуацій, другий приклад насправді є більш читабельним і зрозумілим, ніж перший. «Приховані» шляхи виконання коду мають ті самі виклики деструкторів локальних змінних. Крім того, вони слідують тому самому методу, який працює так само ніби там би були потенційні команди виходу return після виклику кожної функції, які генерують виключні ситуації. Читабельність такого коду краща, оскільки код виконання логіки програми не змішується з кодом обробки помилок, а функція може використовувати можливість повернення значень природним чином на свій розсуд.

Примітки ред.

  1. Exception-Safety in Generic Components. Архів оригіналу за 18 березня 2015. Процитовано 29 серпня 2008.
  2. Abrahams D. Exception-Safety in Generic Components // Lect. Notes Comput. Sci. / G. Goos, J. Hartmanis, J. v. LeeuwenBerlin, Heidelberg, New York, NY, London [etc.]: Springer, 2000. — P. 69–79. — 11 p. — ISSN 0302-9743; 1611-3349doi:10.1007/3-540-39953-4_6
  3. Bjarne Stroustrup. Appendix E: Standard-Library Exception Safety in "The C++ Programming Language" (3rd Edition).Addison-Wesley, ISBN 0-201-88954-4 (PDF). Архів оригіналу (PDF) за 21 березня 2015. Процитовано 19 березня 2015.
  4. Архівована копія. Архів оригіналу за 3 лютого 2009. Процитовано 19 березня 2015.{{cite web}}: Обслуговування CS1: Сторінки з текстом «archived copy» як значення параметру title (посилання)
  5. Exception Safety: Concepts and Techniques [Архівовано 5 березня 2015 у Wayback Machine.], Bjarne Stroustrup, AT&T Labs — Research Florham Park, NJ 07932, USA
  6. «Exception Handling: A False Sense of Security» [Архівовано 8 червня 2007 у Wayback Machine.], Tom Cargill, C++ Report, Nov-Dec 1994

Література ред.