Какой тип исключения я должен выбросить?

18

После просмотра некоторых ссылок по обработке исключений ( 1 , < a href="http://www.cplusplus.com/reference/std/exception/exception/"> 2 и 3 ), я знаю, что программы на C ++ могут выдавать почти все что угодно как исключения ( int , char* , string , exception class). Я знаю, что std::exception является базовым классом для стандартных исключений, выдаваемых программой. Однако я пытаюсь спроектировать блок try ... catch как таковой:

try
{
    MyFunc();
}
catch (certain exceptions)
{
    // deal with the exception accordingly
}
catch (the rest of the exceptions)
{
    // deal with these accordingly
}

while MyFunc() содержит следующее:

void MyFunc()
{
    ...
    if (certain condition is true) throw exception;
    ...
}

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

задан stanigator 21.07.2009 в 08:39
источник

5 ответов

19

Вы бы получили свой собственный класс из std::exception , так что есть какой-то способ единообразного обработка исключений.

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

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

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

    
ответ дан Daniel Earwicker 21.07.2009 в 08:51
12

Вот фрагмент кода, который показывает, как расширить и использовать класс std :: exception: (Кстати, в этом коде есть ошибка, о которой я расскажу позже).

#include <iostream>
#include <string>
#include <exception>

class my_exception : public std::exception
{
public:
   explicit my_exception(const std::string& msg)
      : msg_(msg)
   {}

   virtual ~my_exception() throw() {}

   virtual const char* what() const throw()
   {
      return msg_.c_str();
   }

private:
   std::string msg_;
};

void my_func() throw (my_exception&)
{
  throw my_exception("aaarrrgggg...");
}

int
main()
{
  try
    {
      my_func();
    }
  catch (my_exception& ex)
    {
      std::cout << ex.what() << '\n';
    }
  return 0;
}

Обратите внимание, что конструктор является явным, а деструктор и what () объявляются (используя throw ()), чтобы указать, что они сами не будут генерировать исключения. Вот где ошибка. Гарантируется ли, что вызов msg_.c_str () не выдаст свои собственные исключения? Как насчет строкового конструктора, который мы используем для инициализации msg_? Это также может вызвать исключения. Как мы можем создать класс исключений, который защищен от исключений, создаваемых объектами-членами? Ответ - наследовать от std :: runtime_error или аналогичного подкласса std :: exception. Поэтому правильным способом реализации my_exception будет:

class my_exception : public std::runtime_error
{
public:
    my_exception(const std::string& msg) 
        : std::runtime_error(msg)
    { }
};

Нам не нужно переопределять what (), так как это уже реализовано в std :: runtime_error. Правильная обработка буфера сообщений выполняется с помощью std :: runtime_error, поэтому мы можем быть уверены, что само my_exception не выдаст какую-то неизвестную ошибку во время выполнения.

    
ответ дан Vijay Mathew 21.07.2009 в 11:23
4

Я подумал, что было бы интересно опубликовать реальный код для изменения. Это класс исключений, который использует моя собственная служебная библиотека:

//---------------------------------------------------------------------------
// a_except.h
//
// alib exception handling stuff
//
// Copyright (C) 2008 Neil Butterworth
//---------------------------------------------------------------------------

#ifndef INC_A_EXCEPT_H
#define INC_A_EXCEPT_H

#include "a_base.h"
#include <exception>
#include <sstream>

namespace ALib {

//------------------------------------------------------------------------
// The only exception thrown directly by alib
//------------------------------------------------------------------------

class Exception : public std::exception {

    public:

        Exception( const std::string & msg = "" );
        Exception( const std::string & msg, int line,
                        const std::string & file );

        ~Exception() throw();

        const char *what() const throw();
        const std::string & Msg() const;

        int Line() const;
        const std::string & File() const;

    private:

        std::string mMsg, mFile;
        int mLine;
};

//------------------------------------------------------------------------
// Macro to throw an alib exception with message formatting.
// Remember macro is not in ALib namespace!
//------------------------------------------------------------------------

#define ATHROW( msg )                                               \
{                                                                   \
    std::ostringstream os;                                          \
    os << msg;                                                      \
    throw ALib::Exception( os.str(), __LINE__, __FILE__ );          \
}                                                                   \


}  // namespace

#endif

А это файл .cpp:

//---------------------------------------------------------------------------
// a_except.h
//
// alib exception handling stuff
//
// Copyright (C) 2008 Neil Butterworth
//---------------------------------------------------------------------------

#include "a_except.h"
using std::string;

namespace ALib {

//---------------------------------------------------------------------------
// exception with optional message, filename & line number
//------------------------------------------------------------------------

Exception :: Exception( const string & msg ) 
    : mMsg( msg ),  mFile( "" ), mLine(0) {
}

Exception :: Exception( const string & msg, int line, const string & file ) 
    : mMsg( msg ), mFile( file ), mLine( line ) {
}

//---------------------------------------------------------------------------
// Do nothing
//---------------------------------------------------------------------------

Exception :: ~Exception() throw() {
}

//------------------------------------------------------------------------
// message as C string via standard what() function
//------------------------------------------------------------------------

const char * Exception :: what() const throw() {
    return mMsg.c_str();
}

//------------------------------------------------------------------------
// as above, but as C++ string
//------------------------------------------------------------------------

const string & Exception :: Msg() const {
    return mMsg;
}

//---------------------------------------------------------------------------
// File name & line number
//---------------------------------------------------------------------------

int Exception :: Line() const {
    return mLine;
}

const string & Exception :: File() const {
    return mFile;
}

}  // namespace

// end
    
ответ дан anon 21.07.2009 в 09:33
  • Ваш первый Исключительный ctor должен быть явным, а ATHROW (x) лучше определить для выполнения {std :: stringstream _s; _s << (х); throw ALib :: Exception (_s.str (), __FILE__, __LINE__)} while (0), чтобы избежать проблем с приоритетом работы и разрешить использование ATRHOW (x) в качестве правильного оператора C / C ++. –  Marc Mutz - mmutz 21.07.2009 в 10:08
  • Я высказываю вашу точку зрения на явное. Вызов stringstream _s будет плохой идеей, так как имя зарезервировано в области пространства имен. И в отличие от других, я никогда не обнаружил, что такой способ построения макросов стоит проблемы, так как я никогда не испытывал проблемы без него, возможно, из-за моих других методов программирования. –  Jul 21 '09 at 8:13 21.07.2009 в 10:13
  • Если вы получили из std :: runtime_error, некоторые из них уже будут обработаны для вас! –  Martin York 21.07.2009 в 20:05
1

Если вы можете использовать повышение, то вы должны сделать это. Обратитесь к этой ссылке , чтобы узнать, как использовать исключения буста. Вы также можете спроектировать свою собственную иерархию классов исключений, как указано в других ответах, но вам нужно позаботиться о тонких аспектах, таких как требования nothrow, из метода what. Базовый дизайн того, как это можно сделать на линиях boost :: exception, поясняется ниже: -

#include <string>
#include <memory>
#include <stdexcept>

/************************************************************************/
/* The exception hierarchy is devised into 2 basic layers. 
   System exceptions and Logic exceptions. But Logic exceptions are 
   convertible to the ultimate base 'System' in the system layer.
*************************************************************************/

// the system exception layer
  namespace ExH
  {
    namespace System {
      // This is the only way to make predefined exceptions like
      // std::bad_alloc, etc to appear in the right place of the hierarchy.
      typedef std::exception Exception;
      // we extend the base exception class for polymorphic throw
      class BaseException : public Exception {
      public:
        BaseException() throw() {}
        explicit BaseException(char const* /*desc*/) throw() 
          : Exception()
        {}
        BaseException(BaseException const& that)
          : Exception(that)
        {}
        virtual void raise() const { throw *this; } // used to throw polymorphically
        virtual ~BaseException() throw () {}
      };
      // module level classes compose and catch the descriptive
      // versions of layer-exceptions
      class DescriptiveException : public BaseException  {
      public:
        explicit DescriptiveException (char const* description) throw()
          : description_(description)
        { }
        explicit DescriptiveException (std::string const& description) throw()
          : description_(description.c_str())
        { }

        virtual ~DescriptiveException () throw () {} 

        DescriptiveException (DescriptiveException const& src) throw()
          : BaseException(src)
        {
          this->description_ = src.description_;
        }
        DescriptiveException& operator= (DescriptiveException const& src) throw()
        {
            if (this != &src)
            {
              this->description_ = src.description_;
            }
            return *this;
        }

        /*virtual*/ char const* what () const throw() { return description_; }
        /*virtual*/ void raise() const // used to throw polymorphically
        { throw *this; }
      protected:
        DescriptiveException () throw ();
      private:
        char const* description_; 
      };

    }
  }

// the logic exception layer
  namespace ExH
  {
    namespace Logic
    {

      // Logic::Exception inherits from System::Exception for the
      // following reason. Semantically for some part of the
      // system particular instance of Logic::Exception may seem as
      // opaque System::Exception and the only way to handle it would
      // be to propagate it further. In other words Logic::Exception
      // can be seamlessly "converted" to System::Exception if there is
      // no part of the system interested in handling it.
      //
      class BaseException : public System::BaseException
      {
      public:
        BaseException() throw() {}
        explicit BaseException(char const* desc) throw() 
          : System::BaseException(desc)
        {}
        BaseException(BaseException const& that)
          : System::BaseException(that)
        {}
        virtual void raise() const { throw *this; } // used to throw polymorphically
        virtual ~BaseException() throw () {}
      };
      // module level classes compose and catch the descriptive
      // versions of layer-exceptions
      class DescriptiveException : public BaseException {
      public:
        explicit
        DescriptiveException (char const* description) throw()
          : description_(new std::string(description))
        { }
        explicit
        DescriptiveException (std::string const& description) throw()
          : description_(new std::string(description))
        { }
        DescriptiveException(DescriptiveException const& src) throw()
          : BaseException(src)
        {
            // copy the string
            std::string* str = new std::string(src.description_.get()->c_str());
            description_.reset(str);
        }

        virtual ~DescriptiveException () throw () {}
        /*virtual*/ char const* what () const throw() { return description_->c_str(); }
        /*virtual*/ void raise() const { throw *this; }
      private:
        DescriptiveException& operator= (DescriptiveException const& src) throw(); // copy disabled
        std::auto_ptr<std::string> description_; // do not use std::string, as it can throw
      };
    }
  }


/************************************************************************/
/* Users of the exception hierarchy compose specific exceptions as and
when needed. But they can always be caught at the System::Exception base
class level. Some of the standard conversion examples are demonstrated :-

class MyClass {
public:
  class Exception_ {};
  typedef
  Compound <Exception_, Logic::DescriptiveException>
  Exception;

  class InvalidArgument_ {};
  typedef
  Compound <InvalidArgument_, Exception>
  InvalidArgument;

  class NotInitialized_ {};
  typedef
  Compound <NotInitialized_, Exception>
  NotInitialized;
public:
  void myFunction1() const throw(NotInitialized);
  void myFunctionN() const throw(NotInitialized);
};

void MyClass::myFunction1() const {
  throw NotInitialized("Not Inited!");
}

void MyClass::myFunctionN() const {
  try {
    // call myFunction1()
  }
  catch(NotInitialized const& e){
  // use e
  }
}

This has to be per-class basis. The exposed module will have an exception
specification which will catch all the sub-class exceptions. The calling
module will in turn rely on this exception-specification. This will allow
us to have generalized exception-catching at the application-level and
more specialized exception-catching at the specific module level.       */
/************************************************************************/

// a simple template to compose the exceptions as per conversion requirements
  namespace ExH
  {
    template <typename Type, typename Base>
    class Compound : public Base
    {
    public:
      explicit Compound (char const* description) throw ()
        : Base(description)
      {}
      explicit Compound (std::string const& description) throw ()
        : Base(description)
      {}

      Compound (Compound const& src) throw ()
        : Base(src)
      {}

      virtual ~Compound () throw () {}
    protected:
      Compound () throw () {}
    private:
      Compound& operator= (Compound const& src) throw (); // disable copy
    };

  }
    
ответ дан Abhay 21.07.2009 в 09:23
  • Будет ли использование boost быть хорошей идеей, хотя мой весь проект еще не использует библиотеки boost? –  stanigator 21.07.2009 в 19:27
  • Наверное, нет. Вышеупомянутая иерархия может быть использована и изменена в соответствии с вашими потребностями. У меня был умеренный успех с использованием таких названных уровней eexception в крупных проектах. Кроме того, могут возникнуть небольшие проблемы с переносимостью, если вы используете кросс-компиляцию с использованием boost, поскольку она сильно templatized. Некоторые компиляторы могут не соответствовать стандарту C ++. –  Abhay 22.07.2009 в 05:04
0

Обычно вы должны получать свои собственные классы исключений из std :: exception и его производных, чтобы представлять ошибки, относящиеся к области вашего приложения, например, если вы имеете дело с файлами, у вас должно быть FileNotFoundException, которое включает путь к файлу и другую соответствующую информацию, таким образом Вы можете создавать блоки перехвата для обработки определенных типов ошибок и опускать другие, вы также должны воздерживаться от создания или перехвата исключений, не относящихся к классу.

Посмотрите на похожие иерархии исключений в .NET и Java для узнайте, как моделировать общие ошибки (ошибки файлов, ошибки ввода-вывода, сетевые ошибки и т. д.)

    
ответ дан Diaa Sami 21.07.2009 в 11:46