правильный способ сохранить исключение в переменной - PullRequest
7 голосов
/ 02 июня 2010

У меня есть API, внутри которого есть некоторые исключения для отчетов об ошибках. Базовая структура состоит в том, что у него есть корневой объект исключения, который наследуется от std::exception, затем он выбрасывает некоторый подкласс этого.

Поскольку перехват исключения, генерируемого в одной библиотеке или потоке, и перехват его в другом может привести к неопределенному поведению (по крайней мере, Qt жалуется на это и запрещает его во многих контекстах). Я хотел бы обернуть вызовы библиотеки в функции, которые будут возвращать код состояния, а в случае возникновения исключения - копию объекта исключения.

Каков наилучший способ хранить исключение (с его полиморфным поведением) для последующего использования? Я считаю, что будущий API c ++ 0x использует что-то вроде этого. Так какой же самый лучший подход?

Лучшее, что я могу придумать, - это иметь clone() метод в каждом классе исключений, который будет возвращать указатель на исключение того же типа. Но это не очень обобщенно и вообще не касается стандартных исключений.

Есть мысли?

EDIT : Похоже, что в c ++ 0x будет механизм для этого . Это описывается как "магия библиотеки". Означает ли это, что это не требует каких-либо особенностей языка c ++ 0x? если нет, есть ли реализации, совместимые с c ++ 03?

РЕДАКТИРОВАТЬ : похоже, в boost есть реализация копирования исключений . Я оставлю вопрос открытым для любых не boost::copy_exception ответов.

РЕДАКТИРОВАТЬ : Чтобы устранить озабоченность j_random_hacker относительно основной причины исключения, являющегося ошибкой нехватки памяти. Для этой конкретной библиотеки и набора исключений это не так. Все исключения, полученные из корневого объекта исключения, представляют различные типы ошибок синтаксического анализа, вызванных неправильным пользовательским вводом. Связанные с памятью исключения просто вызовут выброс std::bad_alloc, который рассматривается отдельно.

Ответы [ 5 ]

4 голосов
/ 02 июня 2010

У вас есть то, что было бы тем, что я считаю вашим лучшим, единственный ответ. Вы не можете сохранить ссылку на исходное исключение, потому что оно выйдет из области видимости. Вам просто нужно сделать его копию, и единственный общий способ сделать это - с помощью функции-прототипа, такой как clone ().

Извините.

2 голосов
/ 23 августа 2018

Начиная с C ++ 11, это можно сделать с помощью std :: exception_ptr.

(Я использую это в классе, который делает прерывание std :: thread при условии, что базовая реализация потока является потоком POSIX. Для обработки исключений, которые могут быть выброшены в коде пользователя - что вызывает проблемы, если они выбрасываются определенный критический раздел моей реализации - я сохраняю исключение, используя std :: exception_ptr, а затем выбрасываю его позже после завершения критического раздела.)

Чтобы сохранить исключение, вы перехватываете его и сохраняете в переменной ptr.

std::exception_ptr eptr;
try {
    ... do whatever ...
} catch (...) {
    eptr = std::current_exception();
}

Затем вы можете передавать eptr куда угодно, даже в другие темы (согласно документам - я сам этого не пробовал). Когда пришло время использовать его (т.е. бросить) снова, вы должны сделать следующее:

if (eptr) {
    std::rethrow_exception(eptr);
}

Если вы хотите изучить исключение, вы просто поймаете его.

try {
    if (eptr) {
        std::rethrow_exception(eptr);
    }
} catch (const std::exception& e) {
    ... examine e ...
} catch (...) {
    ... handle any non-standard exceptions ...
}
1 голос
/ 02 июня 2010

Вам разрешено бросать все что угодно, включая указатели. Вы всегда можете сделать что-то вроде этого:

throw new MyException(args);

А затем в обработчике исключений сохраните пойманный указатель, который будет полностью полиморфным (ниже, предполагая, что MyException происходит от std::exception):

try {

   doSomething(); // Might throw MyException*

} catch (std::exception* pEx) {

   // store pEx pointer
}

Вы просто должны быть осторожны с утечками памяти, когда вы делаете это таким образом, поэтому обычно используется бросок по значению и отлов по ссылке.

Подробнее о захвате по указателю: http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8

0 голосов
/ 05 июля 2011

Моя служебная библиотека имеет класс AnyException, который в основном совпадает с boost::any без поддержки приведения. Вместо этого он имеет Throw() член, который выбрасывает исходный сохраненный объект.

struct AnyException {
  template<typename E>
  AnyException(const E& e) 
    : instance(new Exception<E>(e))
  { }

  void Throw() const {
    instance->Throw();
  }

private:
  struct ExceptionBase {
    virtual void Throw() const =0;
    virtual ~ExceptionBase() { }
  };

  template<typename E>
  struct Exception : ExceptionBase {
    Exception(const E& e)
      : instance(e)
    { }

    void Throw() const {
      throw std::move(instance);
    }

  private:
    E instance;
  };
  ExceptionBase* instance;
};

Это упрощение, но это базовая структура. Мой фактический код отключает копирование, и вместо этого имеет семантику перемещения. При необходимости вы можете легко добавить виртуальный метод Clone в ExceptionBase ... поскольку Exception знает исходный тип объекта, он может переслать запрос фактическому конструктору копирования, и вы сразу же получите поддержку для всех копируемых типов, а не только с собственным методом Clone.

Когда это было разработано, оно не предназначалось для хранения перехваченных исключений ... после создания исключения оно распространялось как обычно, поэтому условия нехватки памяти не рассматривались. Однако я полагаю, что вы можете добавить экземпляр std::bad_alloc к объекту и сохранить его непосредственно в этих ситуациях.

struct AnyException {
   template<typename E>
   AnyException(const E& e) {
      try {
          instance.excep = new Exception<E>(e);
          has_exception = true;
      } catch(std::bad_alloc& bad) {
          instance.bad_alloc = bad;
          bas_exception = false;
      }
   }

   //for the case where we are given a bad_alloc to begin with... no point in even trying
   AnyException(const std::bad_alloc& bad) {
     instance.bad_alloc = bad;
     has_exception = false;
   }

   void Throw() const {
     if(has_exception)
         instance.excep->Throw();
     throw instance.bad_alloc;
   }

 private:
   union {
     ExceptionBase* excep;
     std::bad_alloc bad_alloc;
   } instance;
   bool has_exception;
 };

Я вообще не тестировал этот второй бит ... Возможно, мне не хватает чего-то явно очевидного, что помешает его работе.

0 голосов
/ 02 июня 2010

Причина, по которой перехват исключения, генерируемого в одной библиотеке, и перехват его в другой может привести к неопределенному поведению, заключается в том, что эти библиотеки могут быть связаны с различными библиотеками времени выполнения. Если вы вернете исключение из функции, а не выбросите ее, вы не избежите этой проблемы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...