Побудувати стандартні винятки з аргументом нульового вказівника та неможливими постумовами


9

Розглянемо наступну програму:

#include<stdexcept>
#include<iostream>

int main() {
    try {
        throw std::range_error(nullptr);
    } catch(const std::range_error&) {
        std::cout << "Caught!\n";
    }
}

GCC та Clang з libstdc ++ дзвонять std::terminateта скасовують програму із повідомленням

terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_S_construct null not valid

Clang з libc ++ segfaults при побудові винятку.

Див godbolt .

Чи відповідають компілятори стандартам? Відповідний розділ стандарту [diagnostics.range.error] (C ++ 17 N4659) дійсно говорить , що std::range_errorмає const char*перевантаження конструктора , який повинен бути кращим за порівнянні з const std::string&перевантаженням. У розділі також не вказано жодних передумов щодо конструктора, а лише зазначено постумова

Післяумови : strcmp(what(), what_­arg) == 0.

Ця постійна умова завжди має невизначену поведінку, якщо what_argє нульовим покажчиком, то це означає, що моя програма також має невизначене поведінку і що обидва компілятори діють відповідно? Якщо ні, то як слід читати такі неможливі постумови в стандарті?


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

Отже, припускаючи, що це правда, я хотів би зосередити питання більше на тому, як стандарт передбачає цю невизначену поведінку. Чи випливає це з неможливості постумови, що виклик також має невизначену поведінку або попередня умова була просто забута?


Натхненний цим питанням .


Здається, що std :: range_error дозволено зберігати речі за посиланням, тому я не здивуюсь. Виклик what()при nullptrпередачі, ймовірно, спричинить проблеми.
Чіпстер

@Chipster Я не впевнений, що саме ти маєш на увазі, але, подумавши про це ще раз, я думаю, що це має бути невизначена поведінка. Я відредагував своє запитання, щоб зосередити його на тому, як стандартне формулювання передбачає не визначену поведінку.
волоський горіх

Якщо nullptrце буде прийнято, я б подумав, що what()доведеться знешкодити його в якийсь момент, щоб отримати значення. Це було б перенаправленням nullptr, що, в кращому випадку, проблематично, і, безумовно, найважчі.
Чіпстер

Я згоден, хоча. Це має бути невизначена поведінка. Однак, вказуючи це на адекватну відповідь, пояснюючи, чому це не в мене.
Чіпстер

Я думаю, що призначено, що це передумова, щоб аргумент вказував на дійсну рядок C, оскільки strcmpвикористовується для опису значення what_arg. Ось так чи інакше йде відповідний розділ зі стандарту С , на який посилається специфікація <cstring>. Звичайно, формулювання може бути чіткішим.
LF

Відповіді:


0

Від док .

Оскільки при копіюванні std :: range_error не дозволяється викидати винятки, це повідомлення зазвичай зберігається внутрішньо у вигляді окремо виділеного посилального рядка. Ось чому також не існує конструктора, який приймає std :: string &&: він все одно повинен буде копіювати вміст.

Це показує, чому ви отримуєте segfault, api насправді трактує це як справжній рядок. Взагалі в cpp, якщо щось було необов’язковим, буде перевантажений конструктор / функція, яка не приймає те, що йому не потрібно. Таким чином, передача nullptrфункції, яка не документує щось необов'язкове, буде невизначеною поведінкою. Зазвичай API не беруть покажчики за винятком рядків C. Отже, IMHO можна припустити передачу nullptr для функції, яка очікує const char *, що буде не визначеною поведінкою. Більш новіші API можуть віддавати перевагу std::string_viewдля цих випадків.

Оновлення:

Зазвичай справедливо вважати API C ++, який бере вказівник, щоб прийняти NULL. Однак рядки C - це окремий випадок. Поки std::string_viewне було кращого способу їх ефективного передачі. Загалом для API, що приймає const char *, припущення повинно бути таким, що він повинен бути дійсним рядком C. тобто вказівник на послідовність chars, що закінчується символом '\ 0'.

range_errorможе підтвердити, що вказівник не є, nullptrале він не може перевірити, якщо він закінчується символом '\ 0'. Тому краще не проводити ніякої перевірки.

Я не знаю точного формулювання в стандарті, але ця умова, ймовірно, приймається автоматично.


-2

Це повертається до основного питання, чи добре створити std :: string з nullptr? і що це має робити?

www.cplusplus.com говорить

Якщо s - це нульовий покажчик, якщо n == npos, або якщо діапазон, визначений [першим, останнім], недійсний, це спричиняє не визначену поведінку.

Тому, коли

throw std::range_error(nullptr);

називається реалізація намагається зробити щось подібне

store = std::make_shared<std::string>(nullptr);

що не визначено. Який би я вважав помилку (не читаючи фактичного формулювання в стандарті). Натомість розробники вольностей могли зробити щось подібне

if (what_arg)
  store = std::make_shared<std::string>(nullptr);

Але тоді ловцеві доведеться перевірити наявність nullptr what();або він просто впаде там. Таким чином, std::range_errorслід призначити порожній рядок або "(nullptr)", як це робить деякі інші мови.


"Це повертається до основного питання, чи добре створити std :: string з nullptr?" - Я не думаю, що це робить.
Конрад Рудольф

Я не думаю, що стандарт ніде не вказує, що виняток повинен зберігати a, std::stringі std::stringконструктор не повинен вибиратись із роздільною здатністю перевантаження.
волоський горіх

const char *Перевантаження вибирається дозволу перевантаження
MM
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.