Анализатор обнаружил вызов функции 'std::uncaught_exception'. Применение этой функции может привести к неверной логике программы. Начиная с C++17, она признана устаревшей и должна быть заменена на функцию 'std::uncaught_exceptions'.
Функция 'std::uncaught_exception' обычно применяется для того, чтобы понять, вызывается ли код при раскрутке стека. Рассмотрим пример:
constexpr std::string_view defaultSymlinkPath = "system/logs/log.txt"; class Logger { std::string m_fileName; std::ofstream m_fileStream; Logger(const char *filename) : m_fileName { filename } , m_fileStream { m_fileName } { } void Log(std::string_view); ~Logger() { fileStream.close(); if (!std::uncaught_exception()) { std::filesystem::create_symlink(m_fileName, defaultSymlinkPath); } } }; class Calculator { public: int64_t Calc(const std::vector<std::string> ¶ms); // .... ~Calculator() { try { Logger logger("log.txt"); Logger.Log("Calculator destroyed"); } catch (...) { // .... } } } int64_t Process(const std::vector<std::string> ¶ms) { try { Calculator calculator; return Calculator.Calc(params); } catch (...) { // .... } }
В деструкторе класса 'Logger' вызывается функция 'std::filesystem::create_symlink', которая может бросить исключение. Например, если для использования пути 'system/logs/log.txt' у программы недостаточно прав. Если деструктор 'Logger' будет вызван напрямую в результате раскрутки стека, то бросать исключения из этого деструктора нельзя – программа будет аварийно прервана через 'std::terminate'. Поэтому перед вызовом функции программист сделал дополнительную проверку 'if (!std::uncaught_exception())'.
Однако такой код содержит ошибку. Предположим, что функция 'Calc' бросила исключение. Тогда перед выполнением catch-clause произойдёт вызов деструктора 'Calculator'. В нём будет создан экземпляр класса 'Logger', в лог запишется сообщение. Затем будет вызван деструктор 'Logger'. Внутри него произойдёт вызов функции 'std::uncaught_exception'. Эта функция вернёт 'true', потому что исключение, брошенное функцией 'Calc', ещё не перехвачено. Поэтому символическая ссылка для файла с логом не будет создана.
Однако в данном случае можно попробовать создать символическую ссылку. Дело в том, что деструктор 'Logger' будет вызван не напрямую в результате раскрутки стека, а из деструктора 'Calculator'. Поэтому из деструктора 'Logger' можно бросить исключение — нужно только перехватить его до выхода из деструктора 'Calculator'.
Для исправления необходимо воспользоваться функцией 'std::uncaught_exceptions' из C++17:
class Logger { std::string m_fileName; std::ofstream m_fileStream; int m_exceptions = std::uncaught_exceptions(); // <= Logger(const char *filename) : m_fileName { filename } , m_fileStream { m_fileName } { } ~Logger() { fileStream.close(); if (m_exceptions == std::uncaught_exceptions()) { std::filesystem::create_symlink(m_fileName, defaultSymlinkPath); } } };
Теперь при создании объекта класса 'Logger' в поле 'm_exceptions' сохранится текущее количество неперехваченных исключений. Если между созданием объекта и вызовом его деструктора не было брошено новых исключений, то условие будет истинным. Поэтому программа попробует создать символическую ссылку для файла с логом. Если при этом будет брошено исключение, то оно будет перехвачено и обработано в деструкторе 'Calculator', и программа продолжит выполнение.
Данная диагностика классифицируется как: