Почему исключение С++ не предоставляет детали вызова?

#include <fstream>
#include <iostream>
#include <map>

int main(int argc, char** argv) {
    try {
        std::map<std::string, int> m{{"a", 1}, {"b", 2}};
        std::cout << m.at("c") << std::endl;
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

В С++ при извлечении несуществующего ключа карты исключение выглядит как map::at: key not found. Информация о том, что является ключом, не предоставляется.

Кроме того, если вы обращаетесь к несуществующему файлу, сообщение об исключении std::ios_base::failure выглядит как ios_base::clear: unspecified iostream_category error. Имя файла, вызвавшего исключение, не предоставляется. Таким образом, может потребоваться достаточно времени, чтобы выяснить, где исключение, если в проекте используется много map.at() или ifstream is.

В отличие от этого, Python может сказать вам KeyError: 'c' или FileNotFoundError: [Errno 2] No such file or directory: 'foo'.

Это просто соглашение на С++? Спасибо.

Ответ 1

Проблема заключается в объектной модели С++, которая отличается от Python. Чтобы контрастировать, сначала дайте ответ: что хранит Python в объекте исключения для печати ключа? Это ссылка, которая поддерживает этот объект.

Это невозможно сделать просто в С++.

  • std::out_of_range не может хранить указатель или ссылку на ключ как есть. Обработчик для исключения может находиться в удаленном блоке. И это означает, что ключ, скорее всего, вышел из сферы до ввода обработчика. Мы получаем поведение undefined, если к ключу относится.

  • std::out_of_range - это конкретный класс. Не шаблон, например std::map. Он не может легко скопировать ключ в себя. Существует много разных типов Key, и, очевидно, они не могут учитывать их всех. Даже если это возможно, что делать, если ключ чрезвычайно дорог для копирования? Или даже не скопировать? Даже в тех случаях, когда это не так, что, если ключ не конвертируется в строку или печатается?

Вышеуказанные пункты не означают, что это невозможно. std::map::at может вызывать подкласс класса std::out_of_range, который набирает стирание и т.д. Но я надеюсь, что вы видите, что у него нетривиальные накладные расходы. С++ все о том, чтобы не платить за производительность за функции, которые вам не нужны или не используются. Все, кто несет ответственность за то, что накладные расходы безоговорочно не соответствуют этому дизайну.

Ответ 2

Стандарт С++ указывает, что map::at(const key_type& k) запускает исключение std::out_of_range (если значение не находится внутри него); ничего больше... но не меньше; конкретная реализация map::at(), но проблема заключалась бы в том, что k key_type нужно преобразовать в char *. Таким образом, существует несколько вариантов:

  • Не показывать эту информацию
  • std::map не хранит типы без (по крайней мере неявного) преобразования в char *: но было бы требование, применяемое к типу key_store, который не является строгим необходимым
  • Предоставление различных сообщений в std::out_of_range исключении в зависимости от ключа_type: но это решение не всегда отображает ожидаемую информацию

В другой точке зрения std::out_of_range наследуется от std::logic_error; Стандарт С++ различает два основных типа исключений:

  • logic_error: они вызваны ошибками во внутренней логике программа. Теоретически их можно предотвратить.
  • runtime_error: они связаны с событиями, выходящими за рамки программы. Они не могут быть легко предсказаны заранее.

В нашем случае мы можем легко проверить существование элемента (поэтому наш случай фиксируется в этом случае)

Ответ 3

С С++ можно создать персонализированное исключение, наследующее из std:: exception с дополнительной информацией, например filename. Вот пример:

#include <sstream>

class MyException : public std::exception {
  public :
  MyException (std::exception e, std::string filename = "") : std::exception (e), str_ (filename) {}
  MyException (const MyException& e) : std::exception (e), str_ (e.str_) {}
  const char* what () const throw () {
    std::ostringstream oss;
    oss << "exception occured in " << str_ << " : ";
    oss << std::exception::what ();
    return oss.str ().c_str ();
  }
  std::string str_;
};

int main(int argc, char** argv) {
    try {
      try {
          std::map<std::string, int> m{{"a", 1}, {"b", 2}};
          std::cout << m.at("c") << std::endl;
      }
      catch (const std::exception& e) {
          MyException f (e, "main");
          throw (f);
      }
    }
    catch (const MyException& e) {
      std::cerr << e.what() << std::endl;
    }

    return 0;
}

отобразит:

exception occured in main : std::exception