Подтвердить что ты не робот

<system_error> и стандартные/системные коды ошибок

С++ 11 представил заголовок <system_error>, содержащий общую систему для обработки кодов ошибок. std::error_code - это кортеж, содержащий int, код ошибки и ссылку на std::error_category, которая определяет домен ошибки и обработку кода ошибки. Стандартная библиотека состоит из четырех категорий: std::generic_category, std::system_category, std::future_category и std::iostream_category.

Существуют конфликты, по которым будет использоваться категория как на SO и на сайтах С++ при создании std::error_code s/throwing std::system_error с кодом errno и WinAPI:

Однако errno и GetLastError() не могут использовать одну и ту же категорию, в противном случае некоторые коды ошибок будут неоднозначными. Код ошибки 33 является одним из примеров, так как он является как EDOM и ERROR_LOCK_VIOLATION.

Есть даже некоторые места, защищающие пользовательскую категорию для WinAPI, но на данный момент я не могу найти ссылок на это. Эта альтернатива была бы особенно болезненной.

Какую категорию следует использовать с errno и которая должна использоваться с GetLastError(), чтобы

  • std::error_code::default_error_condition()
  • std::error_code::message()

являются несимметричными и соответствуют основному коду ошибки?

4b9b3361

Ответ 1

В стандарте С++:

system_category

текущий проект С++ 17 указывает, что:

Некоторые функции в стандартных отчетах библиотеки библиотеки С++ через объект std::error_code (19.5.2.1). Что объект category() возвращает std::system_category()для ошибок, возникающих из операционной системы,или ссылку на реализацию error_categoryобъект для ошибок, возникающих в другом месте. Реализация определяет возможные значения значения() для каждого из этих error > . [Пример: Для операционных систем, основанных на POSIX, реализациям рекомендуется определить std::system_category()значения, идентичные значениям POSIX errno, с дополнительными значения, определенные в документации по операционным системам.Реализации для операционных систем, которые не основаны на POSIX рекомендуется определять значения, идентичные действующим системных значений. Для ошибок, которые не исходят из операционной системы, реализация может предоставлять перечисления для связанные значения.

Не так ясно:

  • Что должно произойти с errno значениями в Windows?

  • является errno из вызова POSIX "из операционной системы" или предполагается, что он должен быть ограничен вызовами без POSIX?

generic_category

  • std::errc - это перечисление с теми же значениями, что и код ошибки C/POSIX EFOOBAR;

    Значение каждой константы enum errc должно совпадать с значением значение макроса <cerrno>, показанного в приведенном выше обзоре. Независимо от того, реализует ли реализация Макросы <cerrno> не указаны.

  • make_error_code(std::errc) генерирует erro_code, используя generic_category

    error_code make_error_code(errc e) noexcept;

    Возвращает: error_code(static_cast<int>(e), generic_category()).

Это означает, что код ошибки POSIX можно использовать с generic_category. Возможно, что значения, отличные от POSIX, могут некорректно работать с generic_catgeory. На практике они, похоже, поддерживаются реализациями, которые я использовал.

В Boost

Сама система Boost

Документация Boost довольно короткая об этой функции:

Исходное предложение рассматривает категории ошибок как двоичный выбор между errno (то есть POSIX-стиль) и собственной операционной системой коды ошибок.

Кроме того, вы можете найти устаревшее объявление, например:

static const error_category & errno_ecat = generic_category();

В linux_error.hpp:

Чтобы создать error_code после ошибки API: error_code( errno, system_category() )

В windows_error.hpp:

Чтобы создать error_code после ошибки API: error_code( ::GetLastError(), system_category() )

В cygwin_error.hpp:

Чтобы создать error_code после ошибки API: error_code (errno, system_category())

В Windows Boost использует system_category для ошибок errno:

ec = error_code( ERROR_ACCESS_DENIED, system_category() );
ec = error_code( ERROR_ALREADY_EXISTS, system_category() );
ec = error_code( ERROR_BAD_UNIT, system_category() );
ec = error_code( ERROR_WRITE_PROTECT, system_category() );
ec = error_code( WSAEWOULDBLOCK, system_category() );

В ASIO

Мы находим такой код в ASIO:

template <typename ReturnType>
inline ReturnType error_wrapper(ReturnType return_value,
    boost::system::error_code& ec)
{
#if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
  ec = boost::system::error_code(WSAGetLastError(),
      boost::asio::error::get_system_category());
#else
  ec = boost::system::error_code(errno,
      boost::asio::error::get_system_category());
#endif
  return return_value;
}

Мы находим errno как system_category в коде POSIX:

int error = ::pthread_cond_init(&cond_, 0);
boost::system::error_code ec(error,
    boost::asio::error::get_system_category());

Filesystem

Мы находим errno с generic_category в коде POSIX:

if (::chmod(p.c_str(), mode_cast(prms)))
{
  if (ec == 0)
    BOOST_FILESYSTEM_THROW(filesystem_error(
      "boost::filesystem::permissions", p,
      error_code(errno, system::generic_category())));
  else
    ec->assign(errno, system::generic_category());

}

В GNU libstdС++

Filesystem

Найдем errno с generic_category:

if (char* rp = ::realpath(pa.c_str(), buf.get())) {
  [...]
}
if (errno != ENAMETOOLONG) {
  ec.assign(errno, std::generic_category());
  return result;
}

и не используется system_category.

Использование libstdС++

На практике кажется, что вы можете использовать generic_category для не-POSIX errno с libstdС++:

std::error_code a(EADV, std::generic_category());
std::error_code b(EADV, std::system_category());
std::cerr << a.message() << '\n';
std::cerr << b.message() << '\n';

дает:

Advertise error
Advertise error

LibС++

Найдем errno с system_category:

int ec = pthread_join(__t_, 0);
if (ec)
  throw system_error(error_code(ec, system_category()), "thread::join failed");

но не используется generic_category.

Заключение

Я не нахожу какой-либо согласованной картины здесь, но, по-видимому:

  • вы должны использовать system_category при использовании Windows-ошибки в Windows;

  • вы можете безопасно использовать generic_category для значений POSIX errno;

  • вы не должны использовать std::generic_category для значений, отличных от POSIX errno (это может не сработать);

  • Если вы не хотите проверять, является ли ваше значение errno POSIX одним: в системах на основе POSIX, вы должны использовать system_error с errno (строго говоря поддержка для этого не обязана, только поощряется). на POSIX-системах вы можете использовать system_error с errno.

Ответ 2

Я должен смириться с путаницей относительно < system_error > , учитывая, что Крис точно описал, как он работает на http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-1.html и я лично считаю, что стандартный текст на С++ совершенно ясен. Но резюмировать в очень сжатых словах:

Если на POSIX:

generic_category = > Стандартное пространство ошибок POSIX

system_category = > Локальное пространство errno POSIX (обычно расширяет POSIX с проприетарными кодами errno). Используйте strerror(), чтобы развернуть коды в описания строк, возвращаемые message().

Практически на POSIX обе реализации одинаковы и отображают собственное пространство errno.

Если в Windows:

generic_category = > Стандартное пространство errno POSIX, которое возвращается различными функциями эмуляции POSIX в MSVCRT, например fopen() и т.д.

system_category = > Пространство Win32 GetLastError(). Используйте FormatMessage(), чтобы развернуть коды в описания строк, возвращаемые message().

Как использовать < system_error > портативно

std::error_code ec;
#ifdef _WIN32
if((HANDLE)-1 == CreateFile(...))
  ec = std::error_code(GetLastError(), std::system_category());
#else
if(-1 == open(...))
  ec = std::error_code(errno, std::system_category());
#endif
// To test using portable code
if(ec == std::errc::no_such_file_or_directory)
   ...
// To convert into nearest portable error condition (lossy, may fail)
std::error_condition ec2(ec.default_error_condition())

Другие мысли:

Некоторые комментаторы сказали, что < system_error > плохо спроектирован и не должен использоваться. Это просто неправда, она довольно оптимальна с учетом идиоматической практики С++ 03 в момент ее разработки, она генерирует очень жесткий высококачественный фиксированный код задержки на всех основных STL, кроме Dinkumware. Это пользователь, расширяемый для любой произвольной системы кодов ошибок, и стандартизирует унификацию в одну систему разрозненной обработки ошибок сторонней библиотеки.

Правда, сегодня было бы совсем иначе, если бы на момент его создания были доступны глобальные переменные constexpr, и, возможно, это может быть исправлено в стандарте С++, который появится после 17. Но если вы программист, которому нужно перемещаться коды ошибок из сторонних библиотек без потери информации с помощью кода, не написанного для ознакомления с этими сторонними библиотеками, тогда < system_error > является отличным решением.

Считайте его похожим на ключевое слово virtual для обработки кода ошибки сторонней библиотеки - он стирает необходимость в коде, транспортирующем коды третьих лиц, из-за необходимости понимать эти коды. Если у вас есть эта проблема в базе кода - и большинство больших баз кода - то вы должны использовать < system_error > вместо того, чтобы использовать код ошибки или систему перевода, которую вы в настоящее время используете.