2013-04-24 7 views
20

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

Здесь мой код:

#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() возвращала значение, зависящее от того, какой тип ошибки вызывается. Так, например, если бы я вызывал ошибку, которая выглядела как верхний номер, (а), чтобы увидеть, была ли она нулевой, и если бы это было так, она бы установила сообщение, что «у вас не может быть числитель нуля ", но все еще находится внутри функции 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 
    } 

Это, очевидно, не будет работать, но есть способ сделать это так, я не пишу другую функцию для каждого сообщения об ошибке?

+9

Для начала, вызывая 'main' рекурсивно не является законным. – jogojapan

+0

'noZero' не должен (или не должен) быть членом внутри' testException'. Переместите его наружу! – SuperSaiyan

+3

Идея заключается в том, что вы реализуете отдельный класс для каждого типа исключения. Каждый из них переопределяет функцию 'what()' по-другому, и вы, очевидно, можете включать информацию о типах в возвращаемую строку. – jogojapan

ответ

35

Ваш код содержит много заблуждений. Короткий ответ - да, вы можете изменить 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; 

} 

Как вы можете видеть, в случае ошибки выполнение следует до тех пор, «правильное» деление не вводится.

Надеюсь, это поможет.

+0

На самом деле нет исключения, для одного класса «рекомендуется вывести». Совершенно нормально использовать свои собственные классы исключений, если это поможет вам закодировать. Просто имейте в виду, что все в 'std' выдает исключения из' '. – rubenvb

+2

Это действительно не обязательно, но проще (runtime_error предоставляет конструктор для строки, чтобы установить основное сообщение об ошибке) и потенциально более значителен, чем для получения непосредственно из исключения (поскольку вы следуете «предлагаемой» стандартной иерархии исключений) , Кроме того, да, вы можете даже выбросить int, если вы предпочитаете это делать. – Baltasarq

+0

Ничего себе. Хорошо, это довольно круто, спасибо. По общему признанию, мне понадобится время, чтобы понять все это, но это тот момент, когда я тренируюсь. Я думаю, что исключение базового класса является требованием назначения, но я спрошу, можем ли мы использовать runtime_error в его месте. Наше задание после этого охватывает шаблоны, поэтому их использование в этом также должно быть хорошей практикой! Благодарим вас за вклад, это должно помочь в понимании того, как работают исключения. –

2
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(); 
} 

Вы должны всегда использовать STD :: Exception & е. so

catch(std::exception & e) 
{ 
    cout<<e.what(); 
} 
+0

Если я использую 'поймать (Std :: исключение & е)', то я бы застрял по умолчанию .what выход –

+2

Нет, потому что вы должны создать класс класса zeroNumerator: общественный зЬй :: исключение { \t Const символ *, что() const throw() {return "Числитель не может быть 0" }; –

+1

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

0

Вы должны рассмотреть иерархию классов.

Причиной этого может быть неясно при попытке использовать исключения только для передачи строки, но фактическое намерение использовать исключения должно быть механизмом для расширенной обработки исключительных ситуаций. Многие вещи выполняются под капотом среды выполнения C++, в то время как стек вызовов разматывается при перемещении из «броска» в соответствующий «catch».

Примером классов могут быть:

class DivisionError : public std::exception { 
public: 
    DevisionError(const int numerator, const int divider) 
     :numerator(numerator) 
     , divider(divider) 
    { 
    } 
    virtual const char* what() const noexcept { 
     // ... 
    } 
    int GetNumerator() const { return numerator; } 
    int GetDevider() const { return divider; } 
private: 
    const int numerator; 
    const int divider; 
}; 


class BadNumeratorError : public DivisionError { 
public: 
    BadNumeratorError(int numerator, int divider) 
     : DivisionError(numerator, divider) 
    { 
    } 
    virtual const char* what() const noexcept { 
    { 
     // ... 
    } 
}; 


class ZeroDeviderError : public DivisionError { 
public: 
    ZeroDeviderError(int numerator, int divider) 
     : DivisionError(numerator, divider) 
    { 
    } 
    virtual const char* what() const noexcept { 
    { 
     // .... 
    } 
}; 
  • Предоставление различных классов для ошибок, вы даете разработчикам возможность обрабатывать различные ошибки определенным образом (а не только отображать сообщение об ошибке)
  • Предоставление базового класса для типов ошибок, позволяет разработчикам быть более гибкими - быть как можно более конкретными.

В некоторых случаях они должны быть конкретными

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

в других, не

} catch (const DivisionError & ex) { 

Что касается некоторых дополнительных деталей,

  • Вы не должны создайте объекты своих исключений, прежде чем бросать их так, как вы это делали. Независимо от ваших намерений, это просто бесполезно - во всяком случае, вы работаете с копией объекта в секции catch (не путайте путем доступа через ссылку)
  • Использование ссылки const будет хорошим стилем catch (const testException &te), если вы действительно не используете нужен непостоянный объект.
2

Вы можете создать свой собственный класс исключения для ошибок длины, как этот

class MyException : public std::length_error{ 
public: 
    MyException(const int &n):std::length_error(to_string(n)){} 
};