Я видел несколько похожих фрагментов кода, которые выглядели так:
struct MyExcept : std::exception {
explicit MyExcept(const char* m) noexcept : message{m} {}
const char* what() const noexcept override {
return message;
}
const char* message;
};
void foo() {
std::string error;
error += "Some";
error += " Error";
throw MyExcept{error.c_str()};
}
int main() {
try {
foo();
} catch (const MyExcept& e) {
// Is this okay?
std::cout << e.message << std::endl;
}
}
В строке после комментария Is this okay?
мы читаем строку в стиле c, которая была выделена в foo
использовать функцию std::string
, Так как строка разрушается с разматыванием стека, это неопределенное поведение?
Если это действительно неопределенное поведение, что если мы заменим main
работать с этим?
int main() {
foo();
}
Поскольку здесь нет перехвата, компилятору не нужно разматывать стек, и все же выводить результат what()
в консоли и прервите программу. Так это все еще неопределенное поведение?
Ваш первый фрагмент имеет неопределенное поведение. [exception.ctor]/1
:
Когда управление переходит от точки, где исключение выдается обработчику, деструкторы вызываются процессом, указанным в этом разделе, который называется разматыванием стека.
Здесь деструктор или error
называется, вызывая c_str()
стать висящим указателем. Позже разыменовываем его, когда вы используете std::cout
например, неопределенное поведение.
Ваш второй фрагмент в порядке. Нет никаких причин, почему это было бы неопределенным поведением. Вы никогда не звоните what
или сделать что-нибудь еще, что может привести к неопределенному поведению. Единственное, что не определено Стандартом, — это если раскрутка стека происходит или нет, [except.terminate]/2
:
В ситуации, когда соответствующий обработчик не найден, определяется реализацией, будет ли стек разматываться перед
std::terminate()
называется.
Да, это неопределенное поведение. Вы работаете с висящим указателем.
void foo() {
std::string error;
error += "Some";
error += " Error";
throw MyExcept{error.c_str()};
} // << error goes out of scope here and so does the pointer returned
// from c_str()
Поскольку здесь нет перехвата, компилятору не нужно разматывать стек, и все же выводить результат
what()
в консоли и прервите программу. Так это все еще неопределенное поведение?
Поскольку реализация по умолчанию будет использовать std::terminate
и в свою очередь зовет std::abort()
это может быть все еще неопределенное поведение, потому что большинство реализаций стандартного обработчика будут пытаться разыменовать what()
,
Вы можете установить свои собственные обработчики, чтобы избежать этого.
Как уже говорилось, код не определен, так как указатель назначен message
осталось болтаться
std::runtime_error
уже предоставляет решение этой проблемы. Вызовите его конструктор, который принимает std::string
в качестве входных данных, и не переопределять what()
совсем:
struct MyExcept : std::runtime_error {
explicit MyExcept(const std::string & m) noexcept : std::runtime_error(m) {}
};
void foo() {
std::string error;
error += "Some";
error += " Error";
throw MyExcept(error);
}
int main() {
try {
foo();
}
catch (const MyExcept& e) {
std::cout << e.what() << std::endl;
}
}
std::runtime_error
имеет внутренний std::string
чьи данные what()
возвращается по умолчанию, таким образом, избегая висячих проблем.