>
>
>
V1090. The 'std::uncaught_exception' fu…


V1090. The 'std::uncaught_exception' function is deprecated since C++17 and is removed in C++20. Consider replacing this function with 'std::uncaught_exceptions'.

Анализатор обнаружил вызов функции '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> &params);
  // ....
  ~Calculator()
  {
    try
    {
      Logger logger("log.txt");
      Logger.Log("Calculator destroyed");
    }
    catch (...)
    {
      // ....
    }
  }
}

int64_t Process(const std::vector<std::string> &params)
{
  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', и программа продолжит выполнение.

Данная диагностика классифицируется как: