Russian Qt Forum
Ноябрь 23, 2024, 18:25 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3 ... 7   Вниз
  Печать  
Автор Тема: [РЕШЕНО] Обработка ошибок без исключений и с помощью исключений  (Прочитано 50791 раз)
8Observer8
Гость
« : Март 24, 2014, 12:12 »

10/18/2014 По итогу этой темы написал небольшую статью: Стратегия обработки ошибок пользователя с помощью собственных классов исключений

Привет!

Расскажите, пожалуйста, о своём опыте, как вы на практике обрабатываете ошибки в ваших приложениях на Qt. Применяете исключения или нет? Чем чревато применение исключений в приложениях на Qt? В применении исключений больше пользы или вреда? Улучшают ли исключения архитектуру программного проекта?

У меня есть пример. В этом примере открывается текстовый файл, из него читаются данные и данные записываются в другой текстовый файл.

Как я понимаю, есть несколько способов возвращать ошибку и значение из функции:

1) Функция возвращает 0 при отсутствии ошибок и -1 в случае ошибки. Посмотреть код ошибки можно с помощью глобальной переменной errno (глобальной для потока). Значение функции возвращаем через ссылку-параметр функции.

2) Функция возвращает 0 в случае ошибки, так как 0 в C и C++ - это false. Функцию, тогда можно вызывать так: if ( func( ) ). Посмотреть код ошибки можно с помощью глобальной переменной errno. Значение функции возвращаем через ссылку-параметр функции.

3) Функция возвращает, как значение, так и код ошибки через структуру, либо класс, либо std::pair, либо std::tuple

4) Слышал, есть ещё способ с nullptr. Но так пример и не нашёл.

5) Ещё я слышал есть механизм setjmp()/longjmp(). Но его в C++ нельзя использовать, там какая-то замута с конструкторами, деструкторами и стеком. Да и в C его не используют (или не рекомендуют)

6) Функция возвращает 0 при отсутствии ошибок и код ошибки (errno не используется). Значение функции возвращаем через ссылку-параметр функции (можно сделать наоборот, что функция будет возвращать значение, а код ошибки через ссылку-параметр).

Пример без исключений:
Код
C++ (Qt)
#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <iostream>
 
//! Error codes
enum ErrorType {
   //! None errors
   errNone = 0,
 
   //! Error of the opening of a file
   errFileOpen = -1000,
 
   //! Error of the writing of a file
   errFileWrite = -1001,
 
   //! Wrong data
   errDataBad = -5000
};
 
ErrorType readData( const QString &fileName, QString &data );
ErrorType writeResult( const QString &fileName, QString &data );
void showData( const QString &data );
ErrorType showError( const QString &fileName, ErrorType errorCode );
 
/*!
* Working with a text file
*/

int main( int argc, char *argv[] ) {
   QCoreApplication a( argc, argv );
 
   QString data;
   ErrorType errorCode = ErrorType::errNone;
 
   // Read data from a file
   QString iFileName = QString( "input.txt" );
   errorCode = readData( iFileName, data );
   if ( errorCode != ErrorType::errNone ) {
       return showError( iFileName, errorCode );
   }
 
   // Show data on the screen
   showData( data );
 
   // Data processing
   data += QString( ", World" );
 
   // Write data to a file
   QString oFileName = QString( "output.txt" );
   writeResult( oFileName, data );
   if ( errorCode != ErrorType::errNone ) {
       return showError( oFileName, errorCode );
   }
 
   return a.exec( );
}
 
/*!
* Read data from the file
* \param fileName File name
* \param data data
* \return Error code
*/

ErrorType readData( const QString &fileName, QString &data ) {
   QFile file( fileName );
   if ( !file.open( QIODevice::ReadOnly ) ) {
       return ErrorType::errFileOpen;
   }
 
   data = file.readAll( );
 
   file.close( );
   return ErrorType::errNone;
}
 
/*!
* Write data to the file
* \param fileName Fleile name
* \param data Data
* \return Error code
*/

ErrorType writeResult( const QString &fileName, QString &data ) {
   QFile file( fileName );
 
   if ( !file.open( QIODevice::WriteOnly ) ) {
       return ErrorType::errFileOpen;
   }
 
   QTextStream stream( &file );
   stream << data;
   file.close( );
 
   if ( stream.status( ) != QTextStream::Ok ) {
       return ErrorType::errFileWrite;
   }
 
   return ErrorType::errNone;
}
 
/*!
* Show data on the screen
* \param data Data
*/

void showData( const QString &data ) {
   std::cout << data.toStdString( ) << std::endl;
}
 
/*!
* Write the error code to the file
* \param fileName File name
* \param errorCode Error code
* \return Error code
*/

ErrorType showError( const QString &fileName, ErrorType errorCode ) {
   switch ( errorCode ) {
       case ErrorType::errFileOpen:
           std::cerr << "Error: cannot open the file " << fileName.toStdString( ) << std::endl;
           break;
       case ErrorType::errFileWrite:
           std::cerr << "Error: cannot write to the file " << fileName.toStdString( ) << std::endl;
           break;
       case ErrorType::errDataBad:
           std::cerr << "Error: incorrect data in the file " << fileName.toStdString( ) << std::endl;
           break;
       default:
           std::cerr << "Error code: " << errorCode << "; file name: " << fileName.toStdString( ) << std::endl;
           break;
   }
 
   return errorCode;
}
 

Пример с исключениями

Код
C++ (Qt)
#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QVector>
#include <iostream>
#include <exception>
 
void readData( const QString &fileName, QVector<int> &arr )
throw (std::invalid_argument, std::runtime_error);
 
void writeResult( const QString &fileName, QVector<int> &arr )
throw (std::invalid_argument, std::runtime_error);
 
void showData( const QVector<int> &arr );
 
/*!
* Working with a text file
*/

int main( int argc, char *argv[] ) {
   QCoreApplication a( argc, argv );
 
   // Read data from a file
   QString iFileName = QString( "input.txt" );
   QVector<int> arr;
   try {
       readData( iFileName, arr );
   } catch ( const std::exception &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   }
 
   // Show data on the screen
   showData( arr );
 
   // Data processing
   arr.push_back( 100 );
 
   // Write data to a file
   QString oFileName = QString( "output.txt" );
   try {
       writeResult( oFileName, arr );
   } catch ( const std::exception &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   }
 
   return a.exec( );
}
 
/*!
* Read data from the file
* \param fileName File name
* \param data data
*/

void readData( const QString &fileName, QVector<int> &dest )
throw (std::invalid_argument, std::runtime_error) {
   QFile file( fileName );
 
   if ( !file.open( QIODevice::ReadOnly ) ) {
       std::string error = "Unable to open the file " + fileName.toStdString( );
       throw std::invalid_argument( error );
   }
 
   QTextStream stream( &file );
 
   QString input;
   do {
       stream >> input;
       bool ok;
       int value = input.toInt( &ok )
« Последнее редактирование: Октябрь 18, 2014, 11:47 от 8Observer8 » Записан
Bepec
Гость
« Ответ #1 : Март 24, 2014, 13:10 »

Есть ещё подход аля winapi.

Выполняем функцию, запрашивает GetLastError().
Если 0 - значит всё хорошо. Если другое - ппц всё пропало.

Т.е. 1 глобальная функция, возвращающая состояние последней выполненной функции.
Записан
8Observer8
Гость
« Ответ #2 : Март 24, 2014, 15:36 »

Спасибо! Я забыл про этот подход написать. Пользовался им когда-то. Здесь можно ошибки смотреть:
- http://msdn.microsoft.com/en-us/library/cc231199.aspx
- http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
но этот подход системно зависимый, в отличае от глобальной переменной errno.

Так что же по поводу вопросов в начале темы? Исключения в приложениях на Qt это благо? Или лучше их избегать?
« Последнее редактирование: Март 24, 2014, 15:58 от 8Observer8 » Записан
Bepec
Гость
« Ответ #3 : Март 24, 2014, 15:58 »

Это С++. Тут можно что угодно во благо, если умеешь. Но Qt не использует исключения, по слухам это позиция разработчиков.
Записан
8Observer8
Гость
« Ответ #4 : Март 24, 2014, 16:26 »

Спасибо! Этот ответ мне нравится Улыбающийся Значит не зря я копаю в сторону исключений Улыбающийся

Лучше всего об исключениях написано в этой книге (много примеров):
Название: Professional C++
Год: 2011
Автор: Marc Gregoire, Nicholas A. Solter, Scott J. Kleper
Количество страниц: 1104
Язык: английский
Скачать: http://kickass.to/wrox-professional-c-plus-plus-2nd-edition-2011-retail-ebook-debt-t7461950.html
Исходники: http://www.wrox.com/WileyCDA/WroxTitle/Professional-C-2nd-Edition.productCd-0470932449,descCd-DOWNLOAD.html

Я применил их подход к своей программке с чтением и записью в файл. Здесь создаются свои классы исключений, что позволяет, к примеру, указать строку, с некорректными данными: Error reading input.txt at line 2. Применяются наследование и полиморфизм.

Правда в консоль (Application Output) почему-то выводится сообщение: Cannot obtain a handle to the inferior: The parameter is incorrect.

И ещё одна проблема. В классе FileError мне пришлось применить std::string вместо QString здесь:
Код
C++ (Qt)
protected:
   QString mFile;
   std::string mMsg;
};

Потому что я не могу использовать QString тут:
Код
C++ (Qt)
virtual const char* what( ) const noexcept {
   return mMsg.c_str();
}
 

Заранее спасибо за помощь!

Вот весь код проекта:
text_file.pro
Код
C++ (Qt)
#-------------------------------------------------
#
# Project created by QtCreator 2014-03-16T19:25:22
#
#-------------------------------------------------
 
QT       += core
 
QT       -= gui
 
QMAKE_CXXFLAGS += -std=c++11
 
TARGET = text_file
CONFIG   += console
CONFIG   -= app_bundle
 
TEMPLATE = app
 
 
SOURCES += main.cpp
 
HEADERS += \
   FileError.h \
   FileOpenError.h \
   FileReadError.h \
   FileWriteError.h
 

FileError.h
Код
C++ (Qt)
#ifndef FILEERROR_H
#define FILEERROR_H
 
#include <QString>
#include <exception>
#include <string>
 
class FileError : public std::runtime_error {
public:
 
   FileError( const QString &fileIn ) : std::runtime_error( "" ), mFile( fileIn ) {
   }
 
   virtual const char* what( ) const noexcept {
       return mMsg.c_str( );
   }
 
   QString getFileName( ) const {
       return mFile;
   }
 
protected:
   QString mFile;
   std::string mMsg;
};
 
#endif // FILEERROR_H
 

FileOpenError.h
Код
C++ (Qt)
#ifndef FILEOPENERROR_H
#define FILEOPENERROR_H
 
#include <QString>
#include "FileError.h"
 
class FileOpenError : public FileError {
public:
 
   FileOpenError( const QString &fileNameIn ) : FileError( fileNameIn ) {
       //mMsg = QString("Unable to open ") + fileNameIn;
       mMsg = "Unable to open " + fileNameIn.toStdString( );
   }
};
 
#endif // FILEOPENERROR_H
 

FileReadError.h
Код
C++ (Qt)
#ifndef FILEREADERROR_H
#define FILEREADERROR_H
 
#include "FileError.h"
#include <QString>
#include <QTextStream>
#include <sstream>
 
class FileReadError : public FileError {
public:
 
   FileReadError( const QString &fileNameIn, int lineNumIn ) :
   FileError( fileNameIn ), mLineNum( lineNumIn ) {
       //        QTextStream stream(stdout);
 
       //        //stream << QString("Error reading ") << fileNameIn << QString(" at line ") << lineNumIn;
       //        mMsg = QString("Error reading %1 at line %2").arg(fileNameIn).arg(lineNumIn);
       //        stream >> mMsg;
       std::ostringstream ostr;
 
       ostr << "Error reading " << fileNameIn.toStdString( ) << " at line " << lineNumIn;
       mMsg = ostr.str( );
   }
 
   int getLineNum( ) const {
       return mLineNum;
   }
 
protected:
   int mLineNum;
};
 
#endif // FILEREADERROR_H
 

FileWriteError.h
Код
C++ (Qt)
#ifndef FILEWRITEERROR_H
#define FILEWRITEERROR_H
 
#include <QString>
#include "FileError.h"
 
class FileWriteError : public FileError {
public:
 
   FileWriteError( const QString &fileNameIn ) : FileError( fileNameIn ) {
       //mMsg = QString( "Unable to write " ) + fileNameIn;
       mMsg = "Unable to write " + fileNameIn.toStdString( );
   }
};
 
#endif // FILEWRITEERROR_H
 

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QVector>
#include <iostream>
#include "FileOpenError.h"
#include "FileReadError.h"
#include "FileWriteError.h"
 
void readData( const QString &fileName, QVector<int> &arr )
throw (FileOpenError, FileReadError);
 
void writeResult( const QString &fileName, QVector<int> &arr )
throw (FileOpenError, FileWriteError);
 
void showData( const QVector<int> &arr );
 
/*!
* Working with a text file
*/

int main( int argc, char *argv[] ) {
   QCoreApplication a( argc, argv );
 
   // Read data from a file
   QString iFileName = QString( "input.txt" );
   QVector<int> arr;
   try {
       readData( iFileName, arr );
   } catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   }
 
   // Show data on the screen
   showData( arr );
 
   // Data processing
   arr.push_back( 100 );
 
   // Write data to a file
   QString oFileName = QString( "output.txt" );
   try {
       writeResult( oFileName, arr );
   } catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   }
 
   return a.exec( );
}
 
/*!
* Read data from the file
* \param fileName File name
* \param data data
*/

void readData( const QString &fileName, QVector<int> &dest )
throw (FileOpenError, FileReadError) {
   QFile file( fileName );
 
   if ( !file.open( QIODevice::ReadOnly ) ) {
       throw FileOpenError( fileName );
   }
 
   QTextStream stream( &file );
 
   QString input;
   int lineNumber = 0;
   do {
       lineNumber++;
       stream >> input;
       bool ok;
       int value = input.toInt( &ok );
       if ( (ok) && (stream.status( ) == QTextStream::Ok) ) {
           dest.push_back( value );
       } else if ( !stream.atEnd( ) ) {
           throw FileReadError( fileName, lineNumber );
       }
   } while ( !stream.atEnd( ) );
}
 
/*!
* Write data to the file
* \param fileName Fleile name
* \param data Data
* \return Error code
*/

void writeResult( const QString &fileName, QVector<int> &arr )
throw (FileOpenError, FileWriteError) {
   QFile file( fileName );
 
   if ( !file.open( QIODevice::WriteOnly ) ) {
       throw FileOpenError( fileName );
   }
 
   QTextStream stream( &file );
   for ( int i = 0; i < arr.size( ); ++i ) {
       stream << arr[i];
       if ( i != (arr.size( ) - 1) ) {
           stream << " ";
       } else {
           stream << endl;
       }
   }
   file.close( );
 
   if ( stream.status( ) != QTextStream::Ok ) {
       throw FileWriteError( fileName );
   }
}
 
/*!
* Show data on the screen
* \param data Data
*/

void showData( const QVector<int> &arr ) {
   for ( int i = 0; i < arr.size( ); ++i ) {
       std::cout << QString::number( arr[i] ).toStdString( ) << std::endl;
   }
}
 
Записан
OKTA
Гость
« Ответ #5 : Март 24, 2014, 16:46 »

Лично мне кажется, что использовать исключения для обработки чего-то из разряда
Код:
if ( !file.open( QIODevice::ReadOnly ) ) {
        throw FileOpenError( fileName );
    }
крайне неудобно, затратно и бесполезно.. Непонимающий
Записан
8Observer8
Гость
« Ответ #6 : Март 24, 2014, 17:29 »

Затратно по времени набора кода? Но ведь классы придумали, чтобы повторно использовать код. Достаточно скопипастить себе эти классы с исключениями в следующий проект. Ну конечно, можно скопипастить showError() и перечисления с кодами ошибок.
Да, в данном случае можно было и так поступить:
Код
C++ (Qt)
   string inFileName = "input.txt";
   ifstream in;
   in.open( inFileName.c_str( ) );
   if ( !in.is_open( ) ) {
       cerr << "Error: could not open the file " << inFileName.c_str( ) << endl;
       return 1;
   }
 

Но я поставил себе другую задачу. Мне предложили способ обработки без исключений с применением кодов ошибок и функции showError() Я перечислил сколько есть подходов для обработки ошибок в приложениях без исключений (может их намного больше). При использовании других классов со своим способом обработки ошибок, придётся выкручиваться (см. способы обработки ошибок) Исключения унифицируют подход и делают его единообразным. Поэтому это не настолько бесполезно, как может показаться на первый взгляд.

Пользователю функции readData достаточно будет написать следующий код для вывода разного рода ошибок:
Код
C++ (Qt)
   // Read data from a file
   QString iFileName = QString( "input.txt" );
   QVector<int> arr;
   try {
       readData( iFileName, arr );
   } catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   }
 

Записан
Bepec
Гость
« Ответ #7 : Март 24, 2014, 18:46 »

Моё имхо - исключение нужно выбрасывать в случае ИСКЛЮЧИТЕЛЬНОЙ ситуации. Ситуаций исключительных в которых программа может что-то сделать - по пальцам пересчитать.

Потому в %80 программ они нафиг не нужны. А 20% сделаны по привычке.
Записан
8Observer8
Гость
« Ответ #8 : Март 24, 2014, 18:48 »

Отсутствие файла и некорректные данные в нём - это как раз примеры исключительный ситуаций. В этом случае программа может вывести текст на экран.
Записан
Bepec
Гость
« Ответ #9 : Март 24, 2014, 19:22 »

Это стандартная ситуация. Такая же как отсутствие библиотек или некорректные действия пользователя. Исключительная ситуация - это ситуация, которая не предусмотрена программой.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #10 : Март 24, 2014, 19:24 »

Это стандартная ситуация. Такая же как отсутствие библиотек или некорректные действия пользователя. Исключительная ситуация - это ситуация, которая не предусмотрена программой.
Как можно выбросить исключение не предусмотренное программой? Улыбающийся
Записан
_OLEGator_
Гость
« Ответ #11 : Март 24, 2014, 19:31 »

Проблема использования исключений по любому поводу - это потенциальная возможность не обработать какое-то одно исключение и программа упадет.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #12 : Март 24, 2014, 19:36 »

Отсутствие файла и некорректные данные в нём - это как раз примеры исключительный ситуаций. В этом случае программа может вывести текст на экран.
Нет, эта ситуация не является исключительной. Общее правило: если у Вас не возникает никаких трудностей с обычной/нормальной обработкой ошибки - исключение ни к чему. А вот тоже чтение из файла,
Код
C++ (Qt)
void ReadSomeData( stream & strm, std::vector <MyData> & vec )
{
int count;
stream >> count;
if (stream.bad()) throw ReadException;  
for (int i = 0; i < count; ++i) {
  vec.push_back(MyData());
  stream >> vec.back();
}
}
Если ReadSomeData глубоко вложена - то скорее всего исключение уместно, т.к. слишком дорого обойдется передача ошибки "на самый верх"
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #13 : Март 24, 2014, 19:36 »

Проблема использования исключений по любому поводу - это потенциальная возможность не обработать какое-то одно исключение и программа упадет.
Лучше пусть программа завершится с выводом информации о необработанном исключении, чем она будет падать с Segmentation fault. Улыбающийся
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4350



Просмотр профиля
« Ответ #14 : Март 24, 2014, 19:40 »

Общее правило: если у Вас не возникает никаких трудностей с обычной/нормальной обработкой ошибки - исключение ни к чему.
Это что за "общее правило" такое?
Это предложение набор общих слов, ничего не выражающих. Прочтите его. Улыбающийся
Записан
Страниц: [1] 2 3 ... 7   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.717 секунд. Запросов: 23.