21

所以我有一个即将到来的任务来处理异常并在我当前的通讯簿程序中使用它们,大部分作业都围绕着它。我决定尝试处理异常和整个 try catch 的事情,并使用类设计,这是我在几周内最终必须为我的作业做的事情。我有工作代码可以很好地检查异常,但我想知道的是,是否有办法标准化我的错误消息函数(即我的 what() 调用):

这是我的代码:

#include <iostream>
#include <exception>
using namespace std;


class testException: public exception
{
public:
  virtual const char* what() const throw() // my call to the std exception class function (doesn't nessasarily have to be virtual).
  {
  return "You can't divide by zero! Error code number 0, restarting the calculator..."; // my error message
  }

  void noZero();

}myex;  //<-this is just a lazy way to create an object



int main()
{
void noZero();
int a, b;

cout << endl;

cout << "Enter a number to be divided " << endl;

cout << endl;

cin >> a;

cout << endl;

cout << "You entered " << a << " , Now give me a number to divide by " << endl;

cin >> b;

try
{    
    myex.noZero(b); // trys my exception from my class to see if there is an issue
}
catch(testException &te) // if the error is true, then this calls up the eror message and restarts the progrm from the start.
{
    cout << te.what() << endl;
    return main();
}

cout <<endl;

cout << "The two numbers divided are " << (a / b) << endl;  // if no errors are found, then the calculation is performed and the program exits.

return 0;

}

  void testException::noZero(int &b) //my function that tests what I want to check
  { 
    if(b == 0 ) throw myex;  // only need to see if the problem exists, if it does, I throw my exception object, if it doesn't I just move onto the regular code.
  }

我想做的就是让我的 what() 函数可以根据调用的错误类型返回一个值。因此,例如,如果我调用一个看起来是顶部数字 (a) 的错误,以查看它是否为零,如果是,它会将消息设置为“你不能拥有一个零分子”,但仍然在 what() 函数中。这是一个例子:

  virtual const char* what() const throw() 
  if(myex == 1)
  {
      return "You can't have a 0 for the numerator! Error code # 1 "
  }
  else

  return "You can't divide by zero! Error code number 0, restarting the calculator..."; // my error message
  }

这显然行不通,但是有没有办法做到这一点,所以我不会为每个错误消息编写不同的函数?

4

4 回答 4

38

您的代码包含很多误解。简短的回答是肯定的,您可以更改what()以返回您想要的任何内容。但是,让我们一步一步来。

#include <iostream>
#include <exception>
#include <stdexcept>
#include <sstream>
using namespace std;


class DivideByZeroException: public runtime_error {
public:

  DivideByZeroException(int x, int y)
    : runtime_error( "division by zero" ), numerator( x ), denominator( y )
    {}

  virtual const char* what() const throw()
  {
    cnvt.str( "" );

    cnvt << runtime_error::what() << ": " << getNumerator()
         << " / " << getDenominator();

    return cnvt.str().c_str();
  }

  int getNumerator() const
    { return numerator; }

  int getDenominator() const
    { return denominator; }

  template<typename T>
  static T divide(const T& n1, const T& n2)
    {
        if ( n2 == T( 0 ) ) {
            throw DivideByZeroException( n1, n2 );
        } 

        return ( n1 / n2 );
    }

private:
    int numerator;
    int denominator;

    static ostringstream cnvt;
};

ostringstream DivideByZeroException::cnvt;

首先runtime_error,派生自exception是建议的异常类派生自。这是在 stdexcept 标头中声明的。您只需要使用您将在what()方法中返回的消息来初始化它的构造函数。

其次,你应该适当地命名你的类。我知道这只是一个测试,但描述性名称总是有助于阅读和理解您的代码。

如您所见,我更改了构造函数以接受引发异常的除数。您在异常中进行了测试......好吧,我尊重这一点,但作为一个可以从外部调用的静态函数。

最后,what()方法。由于我们将两个数字相除,因此最好显示两个引发异常的数字。实现这一目标的唯一方法是使用 ostringstream。在这里,我们将其设为静态,因此返回指向堆栈对象的指针没有问题(即,具有cnvt局部变量会引入未定义的行为)。

正如您在问题中列出的那样,该程序的其余部分或多或少:

int main()
{
int a, b, result;

cout << endl;

cout << "Enter a number to be divided " << endl;

cout << endl;

cin >> a;

cout << endl;

cout << "You entered " << a << " , Now give me a number to divide by " << endl;

cin >> b;

try
{    
        result = DivideByZeroException::divide( a, b );

    cout << "\nThe two numbers divided are " << result << endl;
}
catch(const DivideByZeroException &e)
{
    cout << e.what() << endl;
}

return 0;

}

如您所见,我已删除您的return main()指示。这是没有意义的,因为你不能main()递归调用。此外,这样做的目的是一个错误:您希望重试引发异常的操作,但这是不可能的,因为异常是不可重入的。但是,您可以稍微更改源代码,以达到相同的效果:

int main()
{
int a, b, result;
bool error;

do  {
    error = false;

    cout << endl;

    cout << "Enter a number to be divided " << endl;

    cout << endl;

    cin >> a;

    cout << endl;

    cout << "You entered " << a << " , Now give me a number to divide by " << endl;

    cin >> b;

    try
    {    
        result = DivideByZeroException::divide( a, b ); // trys my exception from my class to see if there is an issue

        cout << "\nThe two numbers divided are " << result << endl;
    }
    catch(const DivideByZeroException &e) // if the error is true, then this calls up the eror message and restarts the progrm from the start.
    {
        cout << e.what() << endl;
        error = true;
    }
} while( error );

return 0;

}

如您所见,如果出现错误,将执行直到输入“正确”除法。

希望这可以帮助。

于 2013-04-24T09:36:16.660 回答
2

您可以像这样为长度错误创建自己的异常类

class MyException : public std::length_error{
public:
  MyException(const int &n):std::length_error(to_string(n)){}
};
于 2017-08-16T13:08:15.927 回答
1
class zeroNumerator: public std::exception
{
    const char* what() const throw() { return "Numerator can't be 0.\n"; }
};

//...

try
{
  myex.noZero(b); // trys my exception from my class to see if there is an issue
  if(myex==1)
  {
     throw zeroNumerator(); // This would be a class that you create saying that you can't have 0 on the numerator
  }

}
catch(testException &te) 
{
   cout << te.what() << endl;
   return main();
}

You should always use std::exception&e. so do

catch(std::exception & e)
{
  cout<<e.what();
 }
于 2013-04-24T04:16:06.883 回答
1

您应该考虑类的层次结构。

尝试使用异常仅用于传输字符串时,其原因可能并不明显,但使用异常的实际意图应该是一种高级处理异常情况的机制。很多事情都是在 C++ 运行时环境的引擎盖下完成的,而调用堆栈在从“throw”到对应的“catch”时展开。

类的一个例子可能是:

class CalculationError : public std::runtime_error {
public:
    CalculationError(const char * message)
        :runtime_error(message)
    {
    }
};


class ZeroDeviderError : public CalculationError {
public:
    ZeroDeviderError(int numerator, const char * message)
        : CalculationError(message) 
        , numerator (numerator)
    {
    }
    int GetNumerator() const { return numerator; }
private:
    const int numerator;
};
  • 为错误提供不同的类,让开发人员有机会以特定方式处理不同的错误(不仅仅是显示错误消息)
  • 为错误类型提供一个基类,让开发人员更加灵活——根据他们的需要进行具体化。

在某些情况下,他们可能想要具体

} catch (const ZeroDividerError & ex) {
    // ...
}

在其他人中,不是

} catch (const CalculationError & ex) {
    // ...
}

一些额外的细节:

  • 在以您所做的方式抛出之前,您不应该创建异常对象。不管你的意图是什么,它都是无用的——无论如何,你正在使用 catch 部分中的对象副本(不要被通过引用访问混淆——抛出时会创建异常对象的另一个实例)
  • catch (const testException &te) 除非您确实需要非常量对象,否则使用 const 引用将是一种不错的方式。

另外,请注意,用于异常的类型(类)不允许从其复制构造函数中抛出异常,因为如果尝试通过值捕获初始异常,则可以调用复制构造函数(如果不是被编译器忽略),并且这个额外的异常将在捕获初始异常之前中断初始异常处理,这会导致调用 std::terminate。由于允许 C++11 编译器在某些情况下在捕获时消除复制,但省略并不总是明智的,如果明智的话,它只是许可而不是义务(参见https://en.cppreference.com/ w/cpp/language/copy_elision了解详细信息;在 C++11 之前,该语言的标准并未规范此事)。

此外,您应该避免将异常(将它们称为附加)从构造函数中抛出,并移动用于异常的类型(类)的构造函数(将它们称为初始构造函数),因为在抛出对象时可以调用构造函数和移动构造函数类型作为初始异常,然后抛出一个额外的异常将阻止创建初始异常对象,并且初始异常对象将丢失。以及来自复制构造函数的附加异常,当抛出初始异常时,也会导致相同的结果。

于 2017-08-01T11:30:31.570 回答