2009-07-21 46 views
17

Sau khi đi qua một số liên kết về xử lý ngoại lệ (1, 2, và 3), tôi biết rằng các chương trình C++ có thể ném khá nhiều bất cứ điều gì như trường hợp ngoại lệ (int, char*, string, exception lớp). Tôi biết rằng std::exception là lớp cơ sở cho các ngoại lệ tiêu chuẩn được chương trình đưa ra. Tuy nhiên, tôi đang cố gắng để thiết kế một try ... catch khối như vậy:Tôi nên ném loại ngoại lệ nào?

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

khi MyFunc() chứa sau:

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

Vấn đề là ở chỗ một phần của MyFunc chức năng, tôi 'không chắc chắn loại ngoại lệ tôi nên ném. Để giữ cho mã sạch bằng cách thực hiện lớp ngoại lệ của riêng tôi, tôi không có ý tưởng những gì sẽ là một cách tốt để thực hiện các lớp ngoại lệ như vậy.

Trả lời

18

Bạn sẽ lấy được lớp học của riêng bạn từ std::exception, để có một số cách xử lý thống nhất các ngoại lệ.

Nếu điều này có vẻ quá mức cần thiết, bạn có thể ném std::logic_error hoặc một trong các loại ngoại lệ chuẩn khác dành cho các ứng dụng để sử dụng.

Bạn cũng có thể sử dụng các lớp cơ sở này cho trường hợp ngoại lệ cụ thể hơn của riêng bạn: điều này giúp tiết kiệm một ít công việc vì chúng chăm sóc triển khai phương pháp what cho bạn.

Lưu ý rằng hệ thống phân cấp ngoại lệ sâu có thể không sử dụng được, bởi vì về cơ bản bạn đang đoán trước về cách phân loại lỗi và khách hàng của bạn có thể không đồng ý.

1

Nếu bạn có thể sử dụng tăng thì bạn nên làm như vậy. Tham khảo this link về cách sử dụng ngoại lệ tăng cường. Bạn cũng có thể thiết kế phân cấp lớp ngoại lệ của riêng mình như đã nêu trong các câu trả lời khác, nhưng bạn cần phải quan tâm đến các khía cạnh tinh tế như yêu cầu 'không rõ' từ phương thức 'what'. Một thiết kế cơ bản về cách này có thể được thực hiện trên dòng boost :: ngoại lệ được giải thích dưới đây: -

#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 
    }; 

    } 
+0

Would sử dụng tăng là một ý tưởng tốt mặc dù toàn bộ isn dự án của tôi 't sử dụng thư viện tăng chưa? – stanigator

+0

Có lẽ là không. Hệ thống phân cấp trên có thể được sử dụng thay thế và sửa đổi theo nhu cầu của bạn. Tôi đã có thành công vừa phải bằng cách sử dụng các mức độ eexception được đặt tên như vậy trong các dự án lớn. Ngoài ra, có thể có các vấn đề về tính di động nhỏ nếu bạn đang sử dụng tính năng biên dịch chéo bằng cách sử dụng tăng vì nó được tạo khuôn mẫu rất nhiều. Một số trình biên dịch có thể không phù hợp với tiêu chuẩn C++. – Abhay

4

tôi nghĩ nó có thể là thú vị để gửi một số mã thực sự cho một sự thay đổi. Đây là lớp học ngoại lệ thư viện tiện ích của riêng tôi sử dụng:

//--------------------------------------------------------------------------- 
// 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 

Và đây là file 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 
+0

Trường hợp ngoại lệ đầu tiên của bạn nên là 'clear' và' ATHROW (x) 'sẽ được xác định tốt hơn để' do {std :: stringstream _s; _s << (x); ném ALib :: Ngoại lệ (_s.str(), __FILE__, __LINE__)} trong khi (0) 'để tránh các vấn đề ưu tiên của toán tử và để cho phép' ATRHOW (x) 'sử dụng như một câu lệnh C/C++ thích hợp. –

+0

Tôi coi trọng vấn đề của bạn. Việc gọi các chuỗi _s sẽ là một ý tưởng tồi, vì tên được đặt trước ở phạm vi không gian tên. Và không giống như những người khác, tôi chưa bao giờ tìm thấy cách xây dựng các macro có giá trị rắc rối, như tôi chưa bao giờ gặp phải một vấn đề mà không có nó, có lẽ do thực hành lập trình khác của tôi. –

+2

Nếu bạn đã bắt nguồn từ std :: runtime_error, một số điều này đã được xử lý cho bạn! –

12

Đây là một đoạn mã cho thấy làm thế nào để mở rộng và sử dụng std :: ngoại lệ class: (BTW, mã này có lỗi, mà tôi sẽ giải thích sau).

#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; 
} 

Lưu ý rằng hàm tạo được khai báo rõ ràng và hàm hủy() để cho biết rằng chính chúng sẽ không ném ngoại lệ. Đây là nơi mà lỗi là. Có đảm bảo rằng cuộc gọi đến msg_.c_str() sẽ không ném ngoại lệ của riêng nó? Điều gì về constructor chuỗi chúng ta đang sử dụng để khởi tạo msg_? Nó cũng có thể tăng ngoại lệ. Làm thế nào chúng ta có thể thiết kế một lớp ngoại lệ được an toàn khỏi các ngoại lệ do các đối tượng thành viên nêu ra? Câu trả lời là - kế thừa từ std :: runtime_error hoặc một std :: exception subclass.Vì vậy, các cách thích hợp để thực hiện my_exception sẽ là:

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

Chúng tôi không cần phải ghi đè lên những gì(), vì nó đã được thực hiện trong std :: runtime_error. Xử lý đúng đắn bộ đệm thông điệp được thực hiện bởi std :: runtime_error, vì vậy chúng ta có thể chắc chắn rằng chính my_exception sẽ không ném một số lỗi không xác định khi chạy.

0

Thông thường bạn nên lấy các lớp ngoại lệ của mình từ std :: exception và các dẫn xuất của nó đại diện cho các lỗi liên quan đến miền ứng dụng của bạn, ví dụ: nếu bạn xử lý các tệp, bạn nên có FileNotFoundException bao gồm filepath và các thông tin liên quan khác, cách bạn có thể tạo các khối catch để xử lý các loại lỗi nhất định và loại bỏ các loại lỗi khác, bạn cũng nên tránh ném hoặc bắt các ngoại lệ không phải lớp.

Có xem xét phân cấp ngoại lệ tương tự trong .NET và Java để xem làm thế nào để mô hình hóa các lỗi chung (file lỗi, lỗi IO, lỗi mạng, vv)

Các vấn đề liên quan