По- перше, як уже говорилося, все не що ясна в C ++, ІМХО в основному тому , що вимоги і обмеження є кілька більш варіювалася в C ++ , ніж інші мови, особ. C # та Java, які мають "подібні" проблеми з винятками.
Викладу на прикладі std :: stof:
передача порожнього рядка в std :: stof (викине invalid_argument) не помилка програмування
Як я бачу, основний контракт цієї функції полягає в тому, що вона намагається перетворити свій аргумент у плаваючий характер, і про будь-яке невиконання цього повідомлення повідомляється виключенням. Обидва можливі винятки походять від logic_errorпомилки програміста, але не в сенсі "помилка програміста", але в сенсі "вхід ніколи не може бути перетворений на плаву".
Тут можна сказати, що "a" logic_errorвикористовується для вказівки на те, що, враховуючи, що (час виконання) вхід, це завжди помилка, щоб спробувати його перетворити, але це завдання функції визначити це і сказати вам (за винятком).
Бічна примітка: З цього погляду на "a" runtime_error можна розглядати як на щось, що, з огляду на один і той же вхід до функції, теоретично може досягти успіху для різних прогонів. (наприклад, операція з файлами, доступ до БД тощо)
Наступна бічна примітка: Бібліотека регулярних виразів C ++ вирішила отримати помилку, runtime_errorхоча є випадки, коли її можна класифікувати так само, як і тут (невірний шаблон регулярного вираження).
Це просто показує, IMHO, що групування в помилки logic_або runtime_помилка є досить нечіткими в C ++ і насправді не сильно допомагає в загальному випадку (*) - якщо вам потрібно обробляти конкретні помилки, вам, ймовірно, потрібно ловитись нижче двох.
(*): Це не означає , що один шматок коди не повинен бути послідовним, але буде ви кинути runtime_або logic_або custom_щось дійсно не так уже й важливо, як мені здається.
Щоб коментувати і те, stofі bitset:
Обидві функції беруть рядки як аргумент, і в обох випадках це:
- нетривіально, щоб перевірити для абонента, чи дана строка є дійсною (наприклад, найгірший випадок, вам доведеться повторити логіку функції; у випадку бітсету не відразу зрозуміло, чи є порожня рядок дійсною, тому нехай ctor вирішить)
- Це вже відповідальність за функцію "розбору" рядка, тому вона вже повинна перевірити рядок, тому має сенс, що вона повідомляє про помилку, щоб "використовувати" рядок рівномірно (і в обох випадках це виняток) .
Правило, яке часто зустрічається з винятками, - "використовувати виключення лише у виняткових обставинах". Але як функція бібліотеки повинна знати, які обставини є винятковими?
Ця заява, IMHO, має два корені:
Продуктивність : Якщо функція викликається в критичний шлях, а "винятковий" випадок не є винятковим, тобто значна кількість пропусків передбачає кидання винятку, тоді платити кожен раз за механізм вимкнення винятків не має сенсу , і може бути занадто повільним.
Місцевість обробки помилок : Якщо функція викликається і виключення відразу спіймані і оброблені, тобто мало сенсу кидати виняток, так як обробка помилок буде більш багатослівним зcatch , ніж з if.
Приклад:
float readOrDefault;
try {
readOrDefault = stof(...);
} catch(std::exception&) {
// discard execption, just use default value
readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}
Ось де такі функції, як TryParsevs. Parseвступають у дію: Одна версія для того, коли локальний код очікує, що розроблений рядок буде дійсним, та одна версія, коли локальний код передбачає, що насправді очікується (тобто не винятковий), що синтаксичний аналіз буде невдалим.
Дійсно, stofце просто (визначено як) обгортка навколо strtof, тому якщо ви не хочете винятків, використовуйте цю.
Отже, як я повинен вирішити, чи слід використовувати винятки чи ні для певної функції?
ІМХО, у вас є два випадки:
Функція, що нагадує бібліотеку (часто використовується повторно в різних контекстах): ви в основному не можете визначитися. Можливо, надайте обидві версії, можливо, та, яка повідомляє про помилку, та обгортку, яка перетворює повернуту помилку у виняток.
Функція "Application" (специфічна для блоку коду програми, деякі можуть бути використані повторно, але обмежена стилем обробки помилок додатків тощо): Тут це часто має бути досить чітким. Якщо шляхові коди, що викликають функції, обробляють винятки здоровим та корисним способом, використовуйте винятки, щоб повідомити про будь-яку (але див. Нижче) помилку. Якщо код програми легше читати та записувати для стилю повернення помилок, будь-якими способами користуйтеся цим.
Звичайно, між ними знайдуться місця - просто використовуйте те, що потрібно, і пам’ятайте YAGNI.
Нарешті, я думаю, що я повинен повернутися до заяви FAQ,
Не використовуйте кидок, щоб вказати на помилку кодування у використанні функції. Використовуйте assert або інший механізм, щоб або надіслати процес у налагоджувач, або зламати процес ...
Я підписуюся на це для всіх помилок, які є чіткими ознаками того, що щось сильно переплутано або що код виклику явно не знав, що це робить.
Але коли це доречно, часто є специфічним для додатків, тому див. Вище домен бібліотеки проти домену додатка.
Це повертається до питання про те, чи потрібно перевірити передумови виклику , але я не буду в цьому займатися, відповідь уже занадто довго :-)