Анализатор обнаружил вызов функции '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', и программа продолжит выполнение.
Данная диагностика классифицируется как: