我对异常处理的理解很差。如何为我自己的目的定制throw, try, catch语句)。

例如,我定义了一个函数如下:int compare(int a, int b){…}

我希望该函数在a或b为负时抛出一个带有某些消息的异常。

在函数的定义中,我应该怎么做呢?


当前回答

你可以定义一个消息,当某个错误发生时抛出:

throw std::invalid_argument( "received negative value" );

或者你可以这样定义它:

std::runtime_error greatScott("Great Scott!");          
double getEnergySync(int year) {                        
    if (year == 1955 || year == 1885) throw greatScott; 
    return 1.21e9;                                      
}                                                       

通常情况下,你会尝试…像这样捕捉块:

try {
// do something that causes an exception
}catch (std::exception& e){ std::cerr << "exception: " << e.what() << std::endl; }

其他回答

简单:

#include <stdexcept>

int compare( int a, int b ) {
    if ( a < 0 || b < 0 ) {
        throw std::invalid_argument( "received negative value" );
    }
}

标准库提供了一个很好的内置异常对象集合,您可以抛出。请记住,你应该总是通过值抛出,通过引用捕获:

try {
    compare( -1, 3 );
}
catch( const std::invalid_argument& e ) {
    // do stuff with exception... 
}

在每次try之后可以有多个catch()语句,因此如果需要,可以分别处理不同的异常类型。

你也可以重新抛出异常:

catch( const std::invalid_argument& e ) {
    // do something

    // let someone higher up the call stack handle it if they want
    throw;
}

并且不考虑异常类型而捕获异常:

catch( ... ) { };

加上这个答案,因为在这个时候为这个问答创建另一个答案似乎没有什么好处。

在您创建自己的自定义异常的情况下,它派生自std::exception,当您捕获“所有可能的”异常类型时,您应该始终以可能捕获的“最派生的”异常类型开始catch子句。请看下面的例子(不要做什么):

#include <iostream>
#include <string>

using namespace std;

class MyException : public exception
{
public:
    MyException(const string& msg) : m_msg(msg)
    {
        cout << "MyException::MyException - set m_msg to:" << m_msg << endl;
    }

   ~MyException()
   {
        cout << "MyException::~MyException" << endl;
   }

   virtual const char* what() const throw () 
   {
        cout << "MyException::what" << endl;
        return m_msg.c_str();
   }

   const string m_msg;
};

void throwDerivedException()
{
    cout << "throwDerivedException - thrown a derived exception" << endl;
    string execptionMessage("MyException thrown");
    throw (MyException(execptionMessage));
}

void illustrateDerivedExceptionCatch()
{
    cout << "illustrateDerivedExceptionsCatch - start" << endl;
    try 
    {
        throwDerivedException();
    }
    catch (const exception& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an std::exception, e.what:" << e.what() << endl;
        // some additional code due to the fact that std::exception was thrown...
    }
    catch(const MyException& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an MyException, e.what::" << e.what() << endl;
        // some additional code due to the fact that MyException was thrown...
    }

    cout << "illustrateDerivedExceptionsCatch - end" << endl;
}

int main(int argc, char** argv)
{
    cout << "main - start" << endl;
    illustrateDerivedExceptionCatch();
    cout << "main - end" << endl;
    return 0;
}

注意:

The proper order should be vice-versa, i.e.- first you catch (const MyException& e) which is followed by catch (const std::exception& e). As you can see, when you run the program as is, the first catch clause will be executed (which is probably what you did NOT want in the first place). Even though the type caught in the first catch clause is of type std::exception, the "proper" version of what() will be called - cause it is caught by reference (change at least the caught argument std::exception type to be by value - and you will experience the "object slicing" phenomena in action). In case that the "some code due to the fact that XXX exception was thrown..." does important stuff WITH RESPECT to the exception type, there is misbehavior of your code here. This is also relevant if the caught objects were "normal" object like: class Base{}; and class Derived : public Base {}... g++ 7.3.0 on Ubuntu 18.04.1 produces a warning that indicates the mentioned issue:

在函数void illustrateDerivedExceptionCatch()中: item12Linux.cpp:48:2:警告:类型为' MyException '的异常将被捕获 catch(const MyException& e) ^ ~ ~ ~ ~

item12Linux.cpp:43:2:警告:通过之前的处理程序为' std::exception ' Catch (const exception& e) ^ ~ ~ ~ ~

再一次,我想说,这个答案只是为了补充这里描述的其他答案(我认为这一点值得一提,但无法在评论中描述它)。

只需在需要的地方添加throw,并向处理错误的调用方添加try block。按照惯例,你应该只抛出派生自std::exception的东西,所以首先包含<stdexcept>。

int compare(int a, int b) {
    if (a < 0 || b < 0) {
        throw std::invalid_argument("a or b negative");
    }
}

void foo() {
    try {
        compare(-1, 0);
    } catch (const std::invalid_argument& e) {
        // ...
    }
}

另外,查看Boost.Exception。

你可以定义一个消息,当某个错误发生时抛出:

throw std::invalid_argument( "received negative value" );

或者你可以这样定义它:

std::runtime_error greatScott("Great Scott!");          
double getEnergySync(int year) {                        
    if (year == 1955 || year == 1885) throw greatScott; 
    return 1.21e9;                                      
}                                                       

通常情况下,你会尝试…像这样捕捉块:

try {
// do something that causes an exception
}catch (std::exception& e){ std::cerr << "exception: " << e.what() << std::endl; }

虽然这个问题已经很老了,而且已经有人回答过了,但我只想补充一点关于如何在c++ 11中进行适当的异常处理的说明:

使用std::nested_exception和std::throw_with_nested

StackOverflow在这里和这里都有描述,如何在不需要调试器或繁琐的日志记录的情况下,通过简单地编写一个适当的异常处理程序重新抛出嵌套异常,就可以在代码中对异常进行回溯。

由于可以对任何派生异常类执行此操作,因此可以向这样的回溯添加大量信息! 你也可以看看我在GitHub上的MWE,在那里回溯看起来是这样的:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"