Russian Qt Forum

Qt => Уроки и статьи => Тема начата: 8Observer8 от Октябрь 18, 2014, 11:17



Название: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 18, 2014, 11:17
Привет!

Об этой стратегии обработки ошибок пользователя я прочитал в книге Professional C++ (2nd Edition) (http://www.amazon.com/Professional-C-Marc-Gregoire/dp/0470932449) Скачать книгу (99МБ)+код к книге (464КБ) одним архивом (90МБ): https://yadi.sk/d/VCvsky11c6wtP Подробное описание создания собственных классов исключений находится здесь: "Chapter 10. Handling Errors" -> "Exceptions and Polymorphism" -> "Writing Your Own Exception Classes"

Проверка: непустой ли входной аргумент

Функция printArray принимает массив и выбрасывает исключение EmptyError, если входной аргумент пуст:

Output:
Цитировать
Error: empty argument in the function printArray()

main.cpp
Код
C++ (Qt)
#include <vector>
#include <iostream>
#include "freeFunctions.h"
 
int main()
{
   std::vector<int> arr;
 
   try {
       printArray( arr );
   } catch ( const LogicError &e ) {
       std::cerr << e.what() << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Error: unknown expection" << std::endl;
       return 1;
   }
 
   return 0;
}
 

freeFunctions.h
Код
C++ (Qt)
#ifndef FREEFUNCTIONS_H
#define FREEFUNCTIONS_H
 
#include <vector>
#include <iostream>
#include "EmptyArgument.h"
 
void printArray( const std::vector<int> &arr )
throw ( EmptyArgument );
 
#endif // FREEFUNCTIONS_H
 

freeFunctions.cpp
Код
C++ (Qt)
#include "freeFunctions.h"
 
void printArray( const std::vector<int> &arr )
   throw ( EmptyArgument )
{
   std::string functionName = "printArray()";
 
   if ( arr.empty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   for ( std::size_t i = 0; i < arr.size(); ++i ) {
       std::cout << arr[i] << std::endl;
   }
}
 

EmptyArgument.h
Код
C++ (Qt)
#ifndef EMPTYARGUMENT_H
#define EMPTYARGUMENT_H
 
#include <string>
#include "LogicError.h"
 
class EmptyArgument : public LogicError
{
public:
   EmptyArgument( const std::string &functionName ) :
       LogicError( functionName )
   {
       m_message = "Error: empty argument in the "
               "function " + m_functionName;
   }
};
 
#endif // EMPTYARGUMENT_H
 

LogicError.h
Код
C++ (Qt)
#ifndef LOGICERROR_H
#define LOGICERROR_H
 
#include <string>
#include <stdexcept>
 
class LogicError : public std::logic_error
{
public:
 
   LogicError( const std::string &functionName ) :
       std::logic_error( "" ),
       m_functionName( functionName ),
       m_message( "" )
   {
 
   }
 
   virtual ~LogicError( ) throw( )
   {
 
   }
 
   virtual const char *what( ) const throw( )
   {
       return m_message.c_str( );
   }
 
   std::string message( ) const
   {
       return m_message;
   }
 
protected:
   std::string m_functionName;
   std::string m_message;
};
 
#endif // LOGICERROR_H
 

Проверки: открыт ли файл, удачна ли запись, удачно ли чтение

Если файл не открывается, то выбрасывается исключение FileOpenError с текстом, к примеру:
Цитировать
Error: unable to open the file "input.txt" in the "readData()"

Если из файла невозможно прочитать, то - FileReadError с тектом:
Цитировать
Error: unable to read the file "input.txt" in the "readData()"

Если диск переполнен и невозможно записать, то - FileWriteError с текстом:
Цитировать
Error: unable to write to the file "input.txt" in the "readData()"

Все выше перечисленные исключения наследуются от FileError, поэтому мы можем все три исключения отлавливать так:
Код
C++ (Qt)
   try {
       // ...
   }
   catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Error: unknown exception" << std::endl;
       return 1;
   }
 

Исключение EmptyArgument выбрасывается, когда входные данные пустые и наследуется от LogicError

Следующий пример читает имена и фамилии из файла, заполняет массив объектов типа Person и выводит имена на экран:

main.cpp
Код
C++ (Qt)
#include <iostream>
#include <vector>
#include <QString>
#include "freeFunctions.h"
#include "Person.h"
 
int main( )
{
   // Person array for saving
   Person david( "David", "White");
   Person ivan( "Ivan", "Green" );
   std::vector<Person> persons;
   persons.push_back( david );
   persons.push_back( ivan );
 
   try {
       // Parse the person array to the string content
       QString content;
       parsePersonsToStrContent( persons, content );
 
       // Save the string content to the file
       QString fileName = "file.txt";
       writeData( fileName, content );
 
       // Read the string content from the file
       QString readContent;
       readData( fileName, readContent );
 
       // Parse the string content to the person array
       std::vector<Person> readPersons;
       parseContentToPersons( readContent, readPersons );
 
       // Print the person array on the screen
       printData( readPersons );
   } catch ( const LogicError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Error: unknown exception" << std::endl;
       return 1;
   }
 
   return 0;
}
 

Person.h
Код
C++ (Qt)
#ifndef PERSON_H
#define PERSON_H
 
#include <QString>
 
class Person {
public:
 
   Person( const QString &firstName = "",
           const QString &lastName = "" ) :
       m_firstName( firstName ),
       m_lastName( lastName )
   {
 
   }
 
   QString firstName( ) const
   {
       return m_firstName;
   }
 
   QString lastName( ) const
   {
       return m_lastName;
   }
 
   void setFirstName( const QString &firstName )
   {
       m_firstName = firstName;
   }
 
   void setLastName( const QString &lastName )
   {
       m_lastName = lastName;
   }
 
private:
   QString m_firstName;
   QString m_lastName;
};
 
#endif // PERSON_H
 

freeFunctions.h
Код
C++ (Qt)
#ifndef FREEFUNCTIONS_H
#define FREEFUNCTIONS_H
 
#include <vector>
 
#include <QString>
 
#include "FileOpenError.h"
#include "FileReadError.h"
#include "FileWriteError.h"
#include "EmptyArgument.h"
#include "Person.h"
 
void readData( const QString &fileName, QString &content )
throw ( EmptyArgument, FileOpenError, FileReadError );
 
void parseContentToPersons( const QString &content,
                           std::vector<Person> &persons )
throw ( EmptyArgument );
 
void parsePersonsToStrContent( const std::vector<Person> &persons,
                              QString &content)
throw ( EmptyArgument );
 
void writeData( const QString &fileName,
               const QString &content )
throw ( EmptyArgument, FileOpenError, FileWriteError );
 
void printData( const std::vector<Person> &persons )
throw ( EmptyArgument );
 
#endif // FREEFUNCTIONS_H
 

freeFunctions.cpp
Код
C++ (Qt)
#include <iostream>
#include <string>
#include <QFile>
#include <QRegExp>
#include <QTextStream>
#include <QDebug>
#include "freeFunctions.h"
 
void readData(const QString &fileName, QString &content )
throw ( EmptyArgument, FileOpenError, FileReadError )
{
   std::string functionName = "readData()";
 
   // Check argument
   if ( fileName.isEmpty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   // Open the input file for reading
   QFile file( fileName );
   if( !file.open( QIODevice::ReadOnly ) ) {
       throw FileOpenError( fileName.toStdString( ), functionName );
   }
 
   // Read the content from the file
   QByteArray data = file.readAll( );
   if ( data.isEmpty( ) ) {
       throw FileReadError( fileName.toStdString( ), functionName );
   }
 
   content = QString( data );
}
 
void parseContentToPersons( const QString &content, std::vector<Person> &persons )
throw ( EmptyArgument )
{
   std::string functionName = "parseContentToPersons()";
 
   // Check the input argument
   if ( content.isEmpty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   QRegExp regExp("(\\w+) (\\w+)");
   int pos = 0;
   while ( ( pos = regExp.indexIn( content, pos ) ) != -1 ) {
       QString firstName = regExp.cap( 1 );
       QString lastName = regExp.cap( 2 );
       Person person( firstName, lastName );
       persons.push_back( person );
       pos += regExp.matchedLength( );
   }
}
 
void parsePersonsToStrContent( const std::vector<Person> &persons,
                              QString &content)
throw ( EmptyArgument )
{
   std::string functionName = "parsePersonsToStrContent()";
 
   // Check the input argument
   if ( persons.empty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   for ( std::size_t i = 0; i < persons.size( ); ++i ) {
       QString firstName = persons[i].firstName( );
       QString lastName = persons[i].lastName( );
       QString line = QString( "%1 %2\n" ).arg( firstName ).arg( lastName );
       content.append( line );
   }
}
 
void writeData( const QString &fileName, const QString &content )
throw ( EmptyArgument, FileOpenError, FileWriteError )
{
   std::string functionName = "writeData()";
 
   // Check arguments
   if ( fileName.isEmpty( ) || content.isEmpty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   // Open the output file for writing
   QFile file( fileName );
   if ( !( file.open( QIODevice::WriteOnly ) ) ) {
       throw FileOpenError( fileName.toStdString( ), functionName );
   }
 
   // Write data to the output file
   QTextStream stream( &file );
   stream << content;
   if ( stream.status() != QTextStream::Ok ) {
       throw FileWriteError( fileName.toStdString( ), functionName );
   }
}
 
void printData( const std::vector<Person> &persons )
throw ( EmptyArgument )
{
   std::string functionName = "printData()";
 
   // Check the input argument
   if ( persons.empty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   // Print data
   for ( std::size_t i = 0; i < persons.size( ); ++i ) {
       std::cout << "First Name: " << persons[i].firstName( ).toStdString( ) << std::endl;
       std::cout << "Last Name: " << persons[i].lastName( ).toStdString( ) << std::endl;
       std::cout << std::endl;
   }
}
 

FileError.h
Код
C++ (Qt)
#ifndef FILEERROR_H
#define FILEERROR_H
 
#include <string>
#include <stdexcept>
 
class FileError : public std::runtime_error
{
public:
 
   FileError( const std::string &fileName,
              const std::string &functionName) :
       std::runtime_error( "" ),
       m_message( "" ),
       m_fileName( fileName ),
       m_functionName( functionName )
   {
 
   }
 
   virtual ~FileError( ) throw( )
   {
 
   }
 
   virtual const char *what() const throw( )
   {
       return m_message.c_str( );
   }
 
   std::string message( ) const
   {
       return m_message;
   }
 
protected:
   std::string m_message;
   std::string m_fileName;
   std::string m_functionName;
};
 
#endif // FILEERROR_H
 

FileOpenError.h
Код
C++ (Qt)
#ifndef FILEOPENERROR_H
#define FILEOPENERROR_H
 
#include <string>
#include "FileError.h"
 
class FileOpenError : public FileError {
public:
 
   FileOpenError( const std::string &fileName,
                  const std::string &functionName) :
       FileError( fileName, functionName )
   {
       m_message = "Error: unable to open the file \"" +
               m_fileName + "\" in the function \"" +
               m_functionName + "\"";
   }
};
 
#endif // FILEOPENERROR_H
 

FileReadError.h
Код
C++ (Qt)
#ifndef FILEREADERROR_H
#define FILEREADERROR_H
 
#include <string>
#include "FileError.h"
 
class FileReadError : public FileError {
public:
 
   FileReadError( const std::string &fileName,
                  const std::string &functionName ) :
       FileError( fileName, functionName )
   {
       m_message = "Error: unable to read the file \"" + m_fileName +
               "\" in the function \"" + m_functionName + "\"";
   }
};
 
#endif // FILEREADERROR_H
 

FileWriteError.h
Код
C++ (Qt)
#ifndef FILEWRITEERROR_H
#define FILEWRITEERROR_H
 
#include <string>
#include "FileError.h"
 
class FileWriteError : public FileError {
public:
 
   FileWriteError( const std::string &fileName,
                   const std::string &functionName ) :
       FileError( fileName, functionName )
   {
       m_message = "Error: unable to write to the file " +
               m_fileName + " in the function " + m_functionName;
   }
};
 
#endif // FILEWRITEERROR_H
 

EmptyArgument.h
Код
C++ (Qt)
#ifndef EMPTYARGUMENT_H
#define EMPTYARGUMENT_H
 
#include <string>
#include "LogicError.h"
 
class EmptyArgument : public LogicError {
public:
 
   EmptyArgument( const std::string &functionName ) :
       LogicError( functionName )
   {
       m_message = "Error: empty argument in the "
               "function " + m_functionName;
   }
};
 
#endif // EMPTYARGUMENT_H
 

LogicError.h
Код
C++ (Qt)
#ifndef LOGICERROR_H
#define LOGICERROR_H
 
#include <string>
#include <stdexcept>
 
class LogicError : public std::logic_error
{
public:
 
   LogicError( const std::string &functionName ) :
       std::logic_error( "" ),
       m_functionName( functionName ),
       m_message( "" )
   {
 
   }
 
   virtual ~LogicError( ) throw( )
   {
 
   }
 
   virtual const char *what( ) const throw( )
   {
       return m_message.c_str( );
   }
 
   std::string message( ) const
   {
       return m_message;
   }
 
protected:
   std::string m_functionName;
   std::string m_message;
};
 
#endif // LOGICERROR_H
 

Проверка деления на ноль и выхода из диапазона

В моём примере есть функция divide(a, b), где a и b должны быть в диапазоне [-1000, 1000]. Если один из параметров превысил этот диапазон, то функция выбросит исключение с текстом:
Цитировать
Error: values must be from the range [-1000, 1000] in the function Calculator::divide()

Допустим в моём проекте 10 классов, и в каждой по несколько функций, которые должны следить за диапазоном своих аргументов. Мне при написании этих функций достаточно выбросить после проверки исключение:
Код
C++ (Qt)
throw OutOfRange<int>( beginOfRange, endOfRange, functionName );

main.cpp
Код
C++ (Qt)
#include <iostream>
#include "Calculator.h"
 
int main()
{
   Calculator<float> calculator;
 
   try {
       float result = calculator.divide( 24.7f, 3.0f );
       std::cout << "Result = " << result << std::endl;
   } catch ( const LogicError &e ) {
       std::cerr << e.what() << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Error: unknown expection" << std::endl;
       return 1;
   }
 
   return 0;
}

Calculator.h
Код
C++ (Qt)
#ifndef CALCULATOR_H
#define CALCULATOR_H
 
#include <string>
#include "DivideByZero.h"
#include "OutOfRange.h"
 
template <typename Type>
class Calculator
{
public:
   // Divide nums from the range [-1000, 1000]
   Type divide( Type a, Type b )
   throw ( DivideByZero, OutOfRange<int> )
   {
       std::string functionName = "Calculator::divide()";
       if ( b == 0 ) {
           throw DivideByZero( functionName );
       }
 
       const int beginOfRange = -1000;
       const int endOfRange = 1000;
 
       if ( ( a < beginOfRange ) || ( a > endOfRange ) ||
            ( b < beginOfRange ) || ( b > endOfRange ) )
       {
           throw OutOfRange<int>( beginOfRange,
                                  endOfRange,
                                  functionName );
       }
 
       return a / b;
   }
};
 
#endif // CALCULATOR_H

DivideByZero.h
Код
C++ (Qt)
#ifndef DIVIDEBYZERO_H
#define DIVIDEBYZERO_H
 
#include <string>
#include "LogicError.h"
 
class DivideByZero : public LogicError
{
public:
   DivideByZero( const std::string &functionName ) :
       LogicError( functionName )
   {
       m_message = "Error: divide by zero in the "
               "function " + m_functionName;
   }
};
 
#endif // DIVIDEBYZERO_H

OutOfRange.h
Код
C++ (Qt)
#ifndef OUTOFRANGE_H
#define OUTOFRANGE_H
 
#include <string>
#include "LogicError.h"
 
template <typename Type>
class OutOfRange : public LogicError
{
public:
   OutOfRange( Type beginOfRange,
               Type endOfRange,
               const std::string &functionName ) :
       LogicError( functionName )
   {
       m_message = "Error: values must be from the range "
                   "[" + std::to_string( beginOfRange ) +
                   ", " + std::to_string( endOfRange ) + "]"
                   " in the function " + m_functionName;
   }
};
 
#endif // OUTOFRANGE_H

LogicError.h
Код
C++ (Qt)
#ifndef LOGICERROR_H
#define LOGICERROR_H
 
#include <string>
#include <stdexcept>
 
class LogicError : public std::logic_error
{
public:
 
   LogicError( const std::string &functionName ) :
       std::logic_error( "" ),
       m_functionName( functionName ),
       m_message( "" )
   {
 
   }
 
   virtual ~LogicError( ) throw( )
   {
 
   }
 
   virtual const char *what( ) const throw( )
   {
       return m_message.c_str( );
   }
 
   std::string message( ) const
   {
       return m_message;
   }
 
protected:
   std::string m_functionName;
   std::string m_message;
};
 
#endif // LOGICERROR_H

Интересные ссылки:
Правило 8: Не позволяйте исключениям покидать деструкторы (http://www.e-reading.me/chapter.php/1002058/28/Mayers_-_Effektivnoe_ispolzovanie_CPP.html)
Правило 25: Подумайте о поддержке функции swap, не возбуждающей исключений (http://www.e-reading.me/chapter.php/1002058/64/Mayers_-_Effektivnoe_ispolzovanie_CPP.html)
Правило 29: Стремитесь, чтобы программа была безопасна относительно исключений (http://www.e-reading.me/chapter.php/1002058/73/Mayers_-_Effektivnoe_ispolzovanie_CPP.html)
Exception Safety | QtDoc 5.3 (http://qt-project.org/doc/qt-5/exceptionsafety.html)


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 18, 2014, 12:00
Пару дней назад был такой случай: приложение виснет при испускании exception. Поставил печать в catch - до нее дело не доходит. Как такое может быть?


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 18, 2014, 12:07
Пару дней назад был такой случай: приложение виснет при испускании exception. Поставил печать в catch - до нее дело не доходит. Как такое может быть?
Я лишь показал стратегию. Если есть предложения, замечания и т.д., то я могу внести исправления. Ваш вопрос не относится к теме. Создавайте свою. Если  относится, то не говорите загадками, а опишите проблему в моём примере, если она есть


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 18, 2014, 12:28
Я лишь показал стратегию.
А может Вам лучше сначала набраться практического опыта, а потом уж пописывать статейки и "показывать стратегию"? :) А то первый же простейший вопрос ставит "стратега" в тупик  :)



Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 18, 2014, 13:02
Я не придумывал эту стратегию, а перетранслировал из книги. Использую её на практике. Написал здесь, чтобы услышать конструктивную критику и исправить недочёты в демо-примере

Цитировать
приложение виснет
Почему виснет, а не рушится?

Цитировать
А может Вам лучше сначала набраться практического опыта, а потом уж пописывать статейки и "показывать стратегию"?
Я так не считаю. Эта статья будет расти вместе со мной. И если кто-то решит воспользоваться этой версией подхода и столкнётся с проблемой, то сообщит в комментариях и я внесу соответсвующие изменения/дополнения


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 18, 2014, 13:39
Почему виснет, а не рушится?
Ну так приложение не вылетает, ошибки краша нет. Останавливаюсь в отладчике - вижу какие-то потроха ntcore.dll (или др системного модуля - не суть), и это все. Откуда это взялось из моего  кода - хз. Вот я и говорю "виснет".

Вот допустим читал, читал и читал, (кстати вчера молодой человек наглядно показал знакомство с теми ссылками что выше). И тут хирак - вот такая ситуевина. Ну наверное если столько прочел, да еще и статью накатал - разрулит в момент! А иначе зачем читать, да еще и писать?  :)


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: kambala от Октябрь 18, 2014, 14:50
Я не придумывал эту стратегию, а перетранслировал из книги.
таким вещам место в уютненьком [бложике]


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 18, 2014, 18:50
Я не придумывал эту стратегию, а перетранслировал из книги.
таким вещам место в уютненьком [бложике]
Для того чтобы в бложике это кто-то причитал и это кому-нибудь пригодилось нужно время, так как его ещё нужно раскрутить. А здесь раздел подходящий и просмотров не мало. Я написал от средничка к начинающим и средничкам, чтобы вместе найти удобную стратегию контроля ошибок. Я развил идею из книги. Если хотя бы одному человеку это пригодится, то я буду рад. И ещё надеюсь на оценку этой стратегии от профессионалов, замечания, дополнения, критику и т.д. Этот подход, если не ошибаюсь, близок к подходу в C# и Java


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 18, 2014, 19:14
Это даже близко не статья. Просто сообщение.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 18, 2014, 20:09
Статья в коде


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Nidxogg от Октябрь 18, 2014, 20:44
Цитировать
Я развил идею из книги.
Каким образом?


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 18, 2014, 21:16
Тут нет развития. Тут нет даже пояснения. тут имеются ссылки на учебники и код... Печаль.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 19, 2014, 10:21
Ладно, попробуем вникнуть в "перетранслированное"
Код
C++ (Qt)
   // Divide nums from the range [-1000, 1000]
   Type divide( Type a, Type b )
   throw ( DivideByZero, OutOfRange<int> )
   {
       std::string functionName = "Calculator::divide()";
       if ( b == 0 ) {
           throw DivideByZero( functionName );
       }
 
       const int beginOfRange = -1000;
       const int endOfRange = 1000;
 
       if ( ( a < beginOfRange ) || ( a > endOfRange ) ||
            ( b < beginOfRange ) || ( b > endOfRange ) )
       {
           throw OutOfRange<int>( beginOfRange,
                                  endOfRange,
                                  functionName );
       }
 
       return a / b;
   }
};
Я ненавижу такой код. Он создает дутый объем и впечатление "вумности", но реально за ним ничего нет. Откуда я возьму значения диапазона? Чего они вбиты как константы? Если мне надо поделить напр 2000 на 5, я что, уже не могу это сделать и всегда получу исключение? Какой вообще смысл в проверке делимого? И почему делитель проверяется на "слишком большое число" - что это за ошибка такая?

Как всегда в таких случаях насованы template, c претензией на общность. Но (как не всегда, но часто) общность эта липовая. Для чисел с точкой логика совсем другая.

Ах да, ну это же все "для примера". Мы же говорим о "стратегии", общем подходе. Так это тоже неверно, причем принципиально. Операции типа "деление на ноль" слишком мелки чтобы размазывать их в ф-цию, тем более они критичны по скорости (не надо парить про "преждевременную оптимизацию"). Правильно контролировать их прямо в коде, напр
Код
C++ (Qt)
if (b == 0) throw std::overflow_error("invalid b");  // все, с приветом, др разумной обработки нет
a /= b;
 
а часто и обходиться без исключений, особенно для чисел с точкой, напр
Код
C++ (Qt)
template <class T>
T ValidVal( T val, T minV = FUDGE_FACTOR )
{
if (unlikely(fabs(val)) < minV) return minV * (val < 0 ? -1 :  1);
return val;
}
...
a /= ValidVal(b);
Вот кстати типовая задача: есть ф-ция/метод нормализация вектора. Должна ли она бросать исключение?

Ладно, вернемся к нашему юному графоману :)  8Observer8, у Вас есть свое видение проблемы, свой взгляд на вещи, основанный на личном опыте, которым Вы бы хотели поделиться? Я его в упор не вижу, Вы даже списываете совершенно бездарно. Если на Вас книга произвела неизгладимое впечатление - просто дайте ссылку на нее, не надо никаких статей.



Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 19, 2014, 16:41
Цитировать
Откуда я возьму значения диапазона?
Вы сами определяете диапазон и передаёте его исключению OutOfRange. Суть в том, что текст, который будет выводиться находится в OutOfRange. Это намного лучше того, если бы мы в каждой разрабатываемой функции кидали std::out_of_range( " Error: values must be from the range ... " ). Было бы дублирование текста. А свои классы исключений гибкие. Можно задавать параметры. Один из очень удобных параметров - это имя функции. И ещё раз отмечу централизованное управление выводимым текстом. Теперь можно писать свои классы исключений и наследовать их от LogicError. По-моему, очень даже удобно


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 19, 2014, 17:18
Вы сами определяете диапазон и передаёте его исключению OutOfRange.
Каким образом я его определю ??? Вот есть операция
Код
C++ (Qt)
a /= b;
Какие значения "а" можно делить на "b" а какие нельзя?


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 19, 2014, 23:09
В моём примере есть функция divide(a, b), где a и b должны быть в диапазоне [-1000, 1000]. Если один из параметров превысил этот диапазон, то функция выбросит исключение с текстом:
Цитировать
Error: values must be from the range [-1000, 1000] in the function Calculator::divide()

Допустим в моём проекте 10 классов, и в каждой по несколько функций, которые должны следить за диапазоном своих аргументов. Мне при написании этих функций достаточно выбросить после проверки исключение:
Код
C++ (Qt)
throw OutOfRange<int>( beginOfRange,
                                 endOfRange,
                                 functionName );

Я могу так же легко написать любое исключение. Мне достаточно будет унаследовать его от LogicError. К примеру, могу написать исключение EmptyError, которое будет содержать текст, что входной аргумент пуст. Допустим, я разрабатываю функцию printArray, которая принимает массив и выбрасывает исключение EmptyError, если входной аргумент пуст:

Output:
Цитировать
Error: empty argument in the function printArray()

main.cpp
Код
C++ (Qt)
#include <QVector>
#include <iostream>
#include "freeFunctions.h"
 
int main()
{
   QVector<int> arr;
 
   try {
       printArray( arr );
   } catch ( const LogicError &e ) {
       std::cerr << e.what() << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Error: unknown expection" << std::endl;
       return 1;
   }
 
   return 0;
}
 

freeFunctions.h
Код
C++ (Qt)
#ifndef FREEFUNCTIONS_H
#define FREEFUNCTIONS_H
 
#include <QVector>
#include <iostream>
#include "EmptyArgument.h"
 
void printArray( const QVector<int> &arr )
throw ( EmptyArgument );
 
#endif // FREEFUNCTIONS_H
 

freeFunctions.cpp
Код
C++ (Qt)
#include "freeFunctions.h"
 
void printArray(const QVector<int> &arr )
   throw ( EmptyArgument )
{
   std::string functionName = "printArray()";
 
   if ( arr.isEmpty() ) {
       throw EmptyArgument( functionName );
   }
 
   for ( std::size_t i = 0; i < arr.size(); ++i ) {
       std::cout << arr[i] << std::endl;
   }
}
 

EmptyArgument.h
Код
C++ (Qt)
#ifndef EMPTYARGUMENT_H
#define EMPTYARGUMENT_H
 
#include <string>
#include "LogicError.h"
 
class EmptyArgument : public LogicError
{
public:
   EmptyArgument( const std::string &functionName ) :
       LogicError( functionName )
   {
       m_message = "Error: empty argument in the "
               "function " + m_functionName;
   }
};
 
#endif // EMPTYARGUMENT_H
 



Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 20, 2014, 08:13
Статья увеличилась. Следующий раздел будет называться: Проверки: открыт ли файл, удачна ли запись, удачно ли чтение


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 20, 2014, 08:49
В моём примере есть функция divide(a, b), где a и b должны быть в диапазоне [-1000, 1000].
Где Вы видели такую задачу? Значит 1000 можно делить на что-то, а 1001 уже нельзя - это с какой стати? Если стратегия/концепция (что Вы хотели показать) нуждается в столь синтетических примерах - грош ей цена.

Допустим в моём проекте 10 классов, и в каждой по несколько функций, которые должны следить за диапазоном своих аргументов. Мне при написании этих функций достаточно выбросить после проверки исключение:
Код
C++ (Qt)
throw OutOfRange<int>( beginOfRange,
                                 endOfRange,
                                 functionName );
 
Вероятно имелось ввиду что черновая работа (печать строки ошибки и.т.п.) собрана в классе исключения, и ее не придется писать 10 раз. Так нормальный программист решил бы это без всяких книжек (с завлекательным названием "профессионал") просто-напросто создав ф-цию, напр
Код
C++ (Qt)
// использование
if (!ValidRange <int>(value, MIN_VAL, MAX_VAL)) return false;
..
// ф-ция проверки
template <class T>
bool ValidRange( T val, T minV, T maxV, bool useException = true )
{
if (val >= minV && val <= maxV) return true;
std::string errText;
 .. // печатаем ошибку
 
if (useException)
 throw std::out_of_range(errText);
else
 printf("error: %s\n", errText.c_str());  // для отладки
return false;
}
 
Чего не хватает? Зачем выдумывать классы раздувая текст на ровном месте?


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 20, 2014, 09:18
Цитировать
Где Вы видели такую задачу? Значит 1000 можно делить на что-то, а 1001 уже нельзя - это с какой стати? Если стратегия/концепция (что Вы хотели показать) нуждается в столь синтетических примерах - грош ей цена.
Пока не придумал подходящего примера


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 20, 2014, 09:49
Давайте пока забудем про OutOfRange. Я может пока вообще удалю этот пример до лучших времён. В книге был пример с проверками файла на открытие чтение и т.д. А OutOfRange это я уже развил, а пример оказался неудачный

Теперь о файлах и собственных классах исключений

Если файл не открывается, то выбрасывается исключение FileOpenError с текстом, к примеру:
Цитировать
Error: unable to open the file "input.txt" in the "readData()"

Если из файла невозможно прочитать, то - FileReadError с тектом:
Цитировать
Error: unable to read the file "input.txt" in the "readData()"

Если диск переполнен и невозможно записать, то - FileWriteError с текстом:
Цитировать
Error: unable to write to the file "input.txt" in the "readData()"

Все выше перечисленные исключения наследуются от FileError, поэтому мы можем все три исключения отлавливать так:
Код
C++ (Qt)
   try {
       // ...
   }
   catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Error: unknown exception" << std::endl;
       return 1;
   }
 

Исключение EmptyArgument выбрасывается, когда входные данные пустые и наследуется от LogicError

Следующий пример читает имена и фамилии из файла, заполняет массив объектов типа Person и выводит имена на экран:

main.cpp
Код
C++ (Qt)
#include <iostream>
#include <vector>
#include <QString>
#include "freeFunctions.h"
#include "Person.h"
 
int main( )
{
   // Person array for saving
   Person david( "David", "White");
   Person ivan( "Ivan", "Green" );
   std::vector<Person> persons;
   persons.push_back( david );
   persons.push_back( ivan );
 
   try {
       // Parse the person array to the string content
       QString content;
       parsePersonsToStrContent( persons, content );
 
       // Save the string content to the file
       QString fileName = "file.txt";
       writeData( fileName, content );
 
       // Read the string content from the file
       QString readContent;
       readData( fileName, readContent );
 
       // Parse the string content to the person array
       std::vector<Person> readPersons;
       parseContentToPersons( readContent, readPersons );
 
       // Print the person array on the screen
       printData( readPersons );
   } catch ( const LogicError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Error: unknown exception" << std::endl;
       return 1;
   }
 
   return 0;
}
 

Person.h
Код
C++ (Qt)
#ifndef PERSON_H
#define PERSON_H
 
#include <QString>
 
class Person {
public:
 
   Person( const QString &firstName = "",
           const QString &lastName = "" ) :
       m_firstName( firstName ),
       m_lastName( lastName )
   {
 
   }
 
   QString firstName( ) const
   {
       return m_firstName;
   }
 
   QString lastName( ) const
   {
       return m_lastName;
   }
 
   void setFirstName( const QString &firstName )
   {
       m_firstName = firstName;
   }
 
   void setLastName( const QString &lastName )
   {
       m_lastName = lastName;
   }
 
private:
   QString m_firstName;
   QString m_lastName;
};
 
#endif // PERSON_H
 

freeFunctions.h
Код
C++ (Qt)
#ifndef FREEFUNCTIONS_H
#define FREEFUNCTIONS_H
 
#include <vector>
 
#include <QString>
 
#include "FileOpenError.h"
#include "FileReadError.h"
#include "FileWriteError.h"
#include "EmptyArgument.h"
#include "Person.h"
 
void readData( const QString &fileName, QString &content )
throw ( EmptyArgument, FileOpenError, FileReadError );
 
void parseContentToPersons( const QString &content,
                           std::vector<Person> &persons )
throw ( EmptyArgument );
 
void parsePersonsToStrContent( const std::vector<Person> &persons,
                              QString &content)
throw ( EmptyArgument );
 
void writeData( const QString &fileName,
               const QString &content )
throw ( EmptyArgument, FileOpenError, FileWriteError );
 
void printData( const std::vector<Person> &persons )
throw ( EmptyArgument );
 
#endif // FREEFUNCTIONS_H
 

freeFunctions.cpp
Код
C++ (Qt)
#include <iostream>
#include <string>
#include <QFile>
#include <QRegExp>
#include <QTextStream>
#include <QDebug>
#include "freeFunctions.h"
 
void readData(const QString &fileName, QString &content )
throw ( EmptyArgument, FileOpenError, FileReadError )
{
   std::string functionName = "readData()";
 
   // Check argument
   if ( fileName.isEmpty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   // Open the input file for reading
   QFile file( fileName );
   if( !file.open( QIODevice::ReadOnly ) ) {
       throw FileOpenError( fileName.toStdString( ), functionName );
   }
 
   // Read the content from the file
   QByteArray data = file.readAll( );
   if ( data.isEmpty( ) ) {
       throw FileReadError( fileName.toStdString( ), functionName );
   }
 
   content = QString( data );
}
 
void parseContentToPersons( const QString &content, std::vector<Person> &persons )
throw ( EmptyArgument )
{
   std::string functionName = "parseContentToPersons()";
 
   // Check the input argument
   if ( content.isEmpty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   QRegExp regExp("(\\w+) (\\w+)");
   int pos = 0;
   while ( ( pos = regExp.indexIn( content, pos ) ) != -1 ) {
       QString firstName = regExp.cap( 1 );
       QString lastName = regExp.cap( 2 );
       Person person( firstName, lastName );
       persons.push_back( person );
       pos += regExp.matchedLength( );
   }
}
 
void parsePersonsToStrContent( const std::vector<Person> &persons,
                              QString &content)
throw ( EmptyArgument )
{
   std::string functionName = "parsePersonsToStrContent()";
 
   // Check the input argument
   if ( persons.empty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   for ( std::size_t i = 0; i < persons.size( ); ++i ) {
       QString firstName = persons[i].firstName( );
       QString lastName = persons[i].lastName( );
       QString line = QString( "%1 %2\n" ).arg( firstName ).arg( lastName );
       content.append( line );
   }
}
 
void writeData( const QString &fileName, const QString &content )
throw ( EmptyArgument, FileOpenError, FileWriteError )
{
   std::string functionName = "writeData()";
 
   // Check arguments
   if ( fileName.isEmpty( ) || content.isEmpty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   // Open the output file for writing
   QFile file( fileName );
   if ( !( file.open( QIODevice::WriteOnly ) ) ) {
       throw FileOpenError( fileName.toStdString( ), functionName );
   }
 
   // Write data to the output file
   QTextStream stream( &file );
   stream << content;
   if ( stream.status() != QTextStream::Ok ) {
       throw FileWriteError( fileName.toStdString( ), functionName );
   }
}
 
void printData( const std::vector<Person> &persons )
throw ( EmptyArgument )
{
   std::string functionName = "printData()";
 
   // Check the input argument
   if ( persons.empty( ) ) {
       throw EmptyArgument( functionName );
   }
 
   // Print data
   for ( std::size_t i = 0; i < persons.size( ); ++i ) {
       std::cout << "First Name: " << persons[i].firstName( ).toStdString( ) << std::endl;
       std::cout << "Last Name: " << persons[i].lastName( ).toStdString( ) << std::endl;
       std::cout << std::endl;
   }
}
 

FileError.h
Код
C++ (Qt)
#ifndef FILEERROR_H
#define FILEERROR_H
 
#include <string>
#include <stdexcept>
 
class FileError : public std::runtime_error
{
public:
 
   FileError( const std::string &fileName,
              const std::string &functionName) :
       std::runtime_error( "" ),
       m_message( "" ),
       m_fileName( fileName ),
       m_functionName( functionName )
   {
 
   }
 
   virtual ~FileError( ) throw( )
   {
 
   }
 
   virtual const char *what() const throw( )
   {
       return m_message.c_str( );
   }
 
   std::string message( ) const
   {
       return m_message;
   }
 
protected:
   std::string m_message;
   std::string m_fileName;
   std::string m_functionName;
};
 
#endif // FILEERROR_H
 

FileOpenError.h
Код
C++ (Qt)
#ifndef FILEOPENERROR_H
#define FILEOPENERROR_H
 
#include <string>
#include "FileError.h"
 
class FileOpenError : public FileError {
public:
 
   FileOpenError( const std::string &fileName,
                  const std::string &functionName) :
       FileError( fileName, functionName )
   {
       m_message = "Error: unable to open the file \"" +
               m_fileName + "\" in the function \"" +
               m_functionName + "\"";
   }
};
 
#endif // FILEOPENERROR_H
 

FileReadError.h
Код
C++ (Qt)
#ifndef FILEREADERROR_H
#define FILEREADERROR_H
 
#include <string>
#include "FileError.h"
 
class FileReadError : public FileError {
public:
 
   FileReadError( const std::string &fileName,
                  const std::string &functionName ) :
       FileError( fileName, functionName )
   {
       m_message = "Error: unable to read the file \"" + m_fileName +
               "\" in the function \"" + m_functionName + "\"";
   }
};
 
#endif // FILEREADERROR_H
 

FileWriteError.h
Код
C++ (Qt)
#ifndef FILEWRITEERROR_H
#define FILEWRITEERROR_H
 
#include <string>
#include "FileError.h"
 
class FileWriteError : public FileError {
public:
 
   FileWriteError( const std::string &fileName,
                   const std::string &functionName ) :
       FileError( fileName, functionName )
   {
       m_message = "Error: unable to write to the file " +
               m_fileName + " in the function " + m_functionName;
   }
};
 
#endif // FILEWRITEERROR_H
 

EmptyArgument.h
Код
C++ (Qt)
#ifndef EMPTYARGUMENT_H
#define EMPTYARGUMENT_H
 
#include <string>
#include "LogicError.h"
 
class EmptyArgument : public LogicError {
public:
 
   EmptyArgument( const std::string &functionName ) :
       LogicError( functionName )
   {
       m_message = "Error: empty argument in the "
               "function " + m_functionName;
   }
};
 
#endif // EMPTYARGUMENT_H
 

LogicError.h
Код
C++ (Qt)
#ifndef LOGICERROR_H
#define LOGICERROR_H
 
#include <string>
#include <stdexcept>
 
class LogicError : public std::logic_error
{
public:
 
   LogicError( const std::string &functionName ) :
       std::logic_error( "" ),
       m_functionName( functionName ),
       m_message( "" )
   {
 
   }
 
   virtual ~LogicError( ) throw( )
   {
 
   }
 
   virtual const char *what( ) const throw( )
   {
       return m_message.c_str( );
   }
 
   std::string message( ) const
   {
       return m_message;
   }
 
protected:
   std::string m_functionName;
   std::string m_message;
};
 
#endif // LOGICERROR_H
 


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 20, 2014, 10:00
Посмотрите сейчас на мою статью. Она стала лучше


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 20, 2014, 11:36
Огроооомный минус перебивающий все плюсы - что за ересь у вас в архиве с исходниками?


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 20, 2014, 15:11
То что в проекте становится побольше классов, после введения этой концепции, так это не проблема, а преимущество. Разделяй и властвуй!


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 20, 2014, 15:35
Я про то, что у вас в архиве лежит хренова туча хрен знает какого кода с пустыми папками и без пояснений...


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 20, 2014, 22:07
Значит это не мой архив, так как я никаких архивов, в этой теме, не прикреплял


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: m_ax от Октябрь 20, 2014, 22:19
Это даже близко не статья..(
В статье самое главное - это Заключение (Conclusions) (поскольку, по большому счёту, только его и читают)
А заключение должно быть, как женское нижнее бельё - Короткое, прозрачное и возбуждающее)

А здесь этим даже и не пахнет)


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: gil9red от Октябрь 20, 2014, 23:12
Значит это не мой архив, так как я никаких архивов, в этой теме, не прикреплял

Первое сообщение темы имеет ссылку на архив на яндексе


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 21, 2014, 07:01
Бгг... вот это человек... У него в статье прячется вражеский архив, а он и не знает :D


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 21, 2014, 08:54
Спасибо за замечания! Над заключением подумаю

По поводу архива, то там всё нормально. Я этот код скачал отсюда: ссылка (http://www.wrox.com/WileyCDA/WroxTitle/Professional-C-2nd-Edition.productCd-0470932449,descCd-DOWNLOAD.html) Число в названии папки означает номер главы. В некоторых главах книги нет примеров с кодом, поэтому и написано "empty". Вот содержимое папки:

(http://img.pixs.ru/storage/0/2/1/400png_6183298_14364021.png)


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: qate от Октябрь 21, 2014, 09:39
Функция printArray принимает массив и выбрасывает исключение EmptyError, если входной аргумент пуст:

низа чтобы не стал пользоваться такой функцией

в концепции qt исключения получаются как лишними - везде ходят события и отложенный результат


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 21, 2014, 09:52
У меня аж руки опускаются. У него архив с примерами по какому то учебнику и без нормальных названий и он выставляет его с подобием своей статьи, как свои примеры... Что творится в этом человеке?


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 21, 2014, 11:17
То что в проекте становится побольше классов, после введения этой концепции, так это не проблема, а преимущество. Разделяй и властвуй!
Все упирается в то что у Вас нет никакого практического опыта. На абстрактных примерах успешно доказывается любая концепция (в том числе и противоположные). На деле все не так

- много классов - уже плохо (грубо говоря). Вы их сами писали, причем недавно, все помните. Но поставьте себя на место "желающего воспользоваться". Он-то ведь без понятия, ему нужно хотя бы просмотреть каждый класс, запомнить имя класса чтобы его юзать. Это совсем непросто в чужом коде.

- что делают эти многочисленные классы? Печатают ошибки. Это место весьма уязвимо. Нужна или печать в юникоде, или в национальном алфавите или вообще "другая". Напр Вы сочли разумным сообщать имя ф-ции. Но др разработчик может так не считать, напр зачем в месяге для юзера имя ф-ции? И что тогда? Лезть и переделывать весь Ваш дизайн? Стандартный подход: код ошибки + доп данные (часто имя файла) + строка ошибки по умолчанию. И обработчик исключений уже решит что показывать юзверю: то ли сам напечатает, то ли Ваше или вообще ничего

Ну и вообще, как-то вяло, неинтересно. Что Вы хотели показать? Что можно организовывать исключения так и сяк, иногда переиспускать. Ну можно конечно, с этим никто не спорит. Но что здесь такого что надо специально изучать, читать статьи и.т.п.? По моему это и так понятно  :)


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 21, 2014, 12:02
У меня аж руки опускаются. У него архив с примерами по какому то учебнику и без нормальных названий и он выставляет его с подобием своей статьи, как свои примеры... Что творится в этом человеке?

Я подправил, чтобы было ясно:
Цитировать
Об этой стратегии обработки ошибок пользователя я прочитал в книге Professional C++ (2nd Edition) (http://www.amazon.com/Professional-C-Marc-Gregoire/dp/0470932449) Скачать книгу (99МБ)+код к книге (464КБ) одним архивом (90МБ): https://yadi.sk/d/VCvsky11c6wtP Подробное описание создания собственных классов исключений находится здесь: "Chapter 10. Handling Errors" -> "Exceptions and Polymorphism" -> "Writing Your Own Exception Classes"


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 22, 2014, 13:44
Функция printArray принимает массив и выбрасывает исключение EmptyError, если входной аргумент пуст:
низа чтобы не стал пользоваться такой функцией
в концепции qt исключения получаются как лишними - везде ходят события и отложенный результат
Я рассматриваю исключения, как способ показать пользователю, почему завершилась программа. Показать ему текст. Я имею ввиду не только конечного пользователя приложения, но и пользователя класса. Это лучше, чем пользователь увидит креш, который не даст информацию ни ему, ни разработчику. Отловленные ошибки, на мой взгляд, лучше сохранять в лог с полной информацией об источнике (Класс::функция, пояснение о недопустимых параметрах с выводом этих параметров). Хотите ли вы или нет, но большинство функций STL выбрасывает исключения. Для таких функция нужно писать обёртку в виде класса. Может конечно наступит время, когда в Qt будут аналоги абсолютно всех STL функций, которые будут безопасны относительно исключений, но пока такого не произошло. Исключения необходимо отлавливать, показывать пользователю текст ошибки (записывать в лог). Тогда пользователь сможет обратиться к разработчику, а тот сможет быстро найти источник ошибки, так как будет знать какая функция выбросила исключение, тип исключения и какие условия к этому привели

Есть конечно и другой способ без исключений. Это проверять параметры перед тем как отдавать их STL функциям, которые могут выбросить исключение. Просто когда есть свои классы исключений, то лично мне удобно, что текст ошибок находится в классах и не дублируется. Единообразный способ обработки, а не нужно придумывать через что возвращать код ошибки:

Цитата из моей темы: http://www.prog.org.ru/topic_26723_0.html

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 не используется). Значение функции возвращаем через ссылку-параметр функции (можно сделать наоборот, что функция будет возвращать значение, а код ошибки через ссылку-параметр).


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 22, 2014, 14:21
Есть конечно и другой способ без исключений. Это проверять параметры перед тем как отдавать их STL функциям, которые могут выбросить исключение. Просто когда есть свои классы исключений, то лично мне удобно, что текст ошибок находится в классах и не дублируется. Единообразный способ обработки, а не нужно придумывать через что возвращать код ошибки:
Вы прекрасный образец негибкого, догматического мЫшления :) Заучим правило и будем ему неуклонно следовать! С исключениями лучше чем без них! В действительности все зависит от конкретной ситуации. Исключения могут быть очень хороши или очень плохи (как впрочем и все другое). В каждом конкретном случае выбор способа обработки ошибок требует интуиции, и, опять-таки, того самого опыта.

Вот Вы рассказываете как хороши классы исключений. Давайте зарядим на каждый случай свой класс! В действительности это достаточно спорно/проблематично. Напр я (и полагаю - не только) совсем не горю желанием иметь батарею catch'ей. Ну если где-то в одном месте - ладно, потерплю. Но если во многих - это так загадит код что он станет невыносимым. Не надо спешить с выводами (тем более в виде статей) всего лишь прочитав книжку (пусть самую хорошую).


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: vulko от Октябрь 22, 2014, 14:27
В каждом конкретном случае выбор способа обработки ошибок требует интуиции

Фраза века!

В каждом конкретном случае говнокодинг и говнофиксинг требует интуиции. Потому что если анализировать и думать, то наговнокодить сложнее. А тут интуитивно понатыкал говнокода, а вдруг пофиксится!))
Индусы именно так и работают)))


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 22, 2014, 14:47
Vulko а вот тут вы неправы. Igors дело говорит. А уж Observer для меня стал нарицательным именем :D


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: vulko от Октябрь 22, 2014, 15:04
Vulko а вот тут вы неправы. Igors дело говорит. А уж Observer для меня стал нарицательным именем :D

Насчет того что не везде нужна обработка исключений я не спорю, по делу сказал.

Но вот про интуицию это конечно ппц)))
Вопрос заключается в том нужны ли они для разработчика/юзера или не нужны.
И ответ зависит лишь от того, возможны ли потенциальные ошибки в рантайме. Например юзер ввел неверно что-то. Программист вышел за пределы массива. И т.п.
Причем ошибки юзера можно свести на нет проверками, исключения не особо нужны. Остаются разработчики. Если для разработчика АПИ будет скрыто, неплохо бы добавить исключения. А если для разработчика оно открыто, то не обязательно.

Какая нафик интуиция вообще?!


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 22, 2014, 15:09
Цитировать
Напр я (и полагаю - не только) совсем не горю желанием иметь батарею catch'ей
Их можно все унаследовать от одного класса, например, пусть он называется LogicError:
Код
C++ (Qt)
   try {
       // Parse the person array to the Json-content
       QJsonDocument content;
       parsePersonsToContent( persons, content );
   } catch ( const LogicError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   }
   catch ( ... ) {
       std::cerr << "Error: unknown exception" << std::endl;
       return 1;
   }

Опять же повторюсь, что это способ завершить программу без креша, и с максимально полной информацией. Конечно же, если команда конкретной фирмы за это, то здорово! А когда один работаешь, то ещё лучше. Мне лично пока такая схема очень нравится. Пока не вижу в ней недостатков, так как не видел конкретных ситуаций, примеров. На мой взляд, такая схема завершения без креша намного лучше, чем придумывать свою схему возвращения кодов ошибок. О вкусах не спорят


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 22, 2014, 15:26
Qt возвращает коды ошибок - прекрасно! Можно написать обёртку, которая выкидывает исключение. Но я далеко не всегда так делаю. Просто показываю сообщение QMessageBox. Ошибки нужно обрабатывать всегда, но комбинировать намного лучше, чем следовать одной догме. В одном месте программы мне удобно показать QMessageBox с текстом "Некорректны ввод. Введите снова" без всяких исключений. А вот в другом месте программы я вызываю метод своего объекта. Я знаю, что он выбрасывает исключения унаследованные от класса LogicError. Я пишу вызов метода в блоке try{}. Показал текст, который выбросило исключение в QMessageBox (сохранил в лог), завершил приложение


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 22, 2014, 15:27
Их можно все унаследовать от одного класса, например, пусть он называется LogicError:
Код
C++ (Qt)
   try {
       // Parse the person array to the Json-content
       QJsonDocument content;
       parsePersonsToContent( persons, content );
   } catch ( const LogicError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   }
   catch ( ... ) {
       std::cerr << "Error: unknown exception" << std::endl;
       return 1;
   }
А тогда чего городились многочисленные классы? Типа - захочет, пусть разбирается с каждым в своем catch, а нет - вывел общую ошибку (как здесь) и все. Не так ли ? :)  Но это неудачно (или просто неверно), если интересно объясню почему.



Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 22, 2014, 15:40
Цитировать
А тогда чего городились многочисленные классы?
Классы городились, потому что там текст разный и параметры конструкторы могут разные принимать. И для удобства различия по имени. Наследуются от одного или от разных классов по связи, чтобы catch'ев много не писать. Вот у меня в проекте есть базовый класс FileError, а от него наследуются: FileOpenError, FileWriteError, FileReadError, которые содержат просто разный текст и в качестве параметра конструкторам передаётся имя функции, которое выбросило исключение. Текст показали, завершили приложение


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 22, 2014, 17:19
Классы городились, потому что там текст разный ..
Это плохое, поверхностное решение


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 22, 2014, 18:34
to Vulko:
Именно интуиция разработчика позволяет создать универсальное и легковесное решение :D
Разработчику без интуиции очень сложно, я бы сказал невозможно жить :)

PS почитайте что такое интуиция и с удивлением поймёте, что интуиция есть результат осмысления опыта :)



Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Old от Октябрь 22, 2014, 21:26
Вот про что я говрил в теме "Мать и матика". Очередная тема в которой вы совсем не разбираетесь, но пытетесь делать заключения и даже давать рекомендации.

Но это неудачно (или просто неверно), если интересно объясню почему.
Конечно, будьте любезны обьяснить свои фантазии.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: vulko от Октябрь 23, 2014, 08:01
to Vulko:
Именно интуиция разработчика позволяет создать универсальное и легковесное решение :D
Разработчику без интуиции очень сложно, я бы сказал невозможно жить :)

PS почитайте что такое интуиция и с удивлением поймёте, что интуиция есть результат осмысления опыта :)

Результатом осмысления опыта, является осмысленный опыт, позволяющий осознанно принимать решения.

А интуиция это нечто совершенно другое.
Есть автоматизм, который приходит с опытом и тренировками, а есть интуиция.

Это только у википедиков интуиция есть результат осмысления опыта. В жизни все по другому.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: vulko от Октябрь 23, 2014, 08:04
Вот про что я говрил в теме "Мать и матика". Очередная тема в которой вы совсем не разбираетесь, но пытетесь делать заключения и даже давать рекомендации.

Но это неудачно (или просто неверно), если интересно объясню почему.
Конечно, будьте любезны обьяснить свои фантазии.


Да... этот профессиАнал в каждой тебе путает людей своими "познаниями". Банить имхо таких надо.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 23, 2014, 08:37
Интуиция это и есть наш опыт в неявной форме. Профессиональный строитель забьёт гвоздь 1 ударом. Профессиональный портной сделает закройку парой движений. Профессиональный инженер даже без уровня заценит размер помещения с точностью до 20 см и увидит кривизну. А профессиональный программист при просмотре кода зацепится за несоответствие и закроет баг, не дожидаясь слива в интернете.

Закроем тему интуиции.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: vulko от Октябрь 23, 2014, 09:23
Интуиция это и есть наш опыт в неявной форме. Профессиональный строитель забьёт гвоздь 1 ударом. Профессиональный портной сделает закройку парой движений. Профессиональный инженер даже без уровня заценит размер помещения с точностью до 20 см и увидит кривизну. А профессиональный программист при просмотре кода зацепится за несоответствие и закроет баг, не дожидаясь слива в интернете.

Закроем тему интуиции.

Интуиция, это когда программист не увидев кода скажет где несоответсвие и баг.
А то что ты описал, это опыт.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Bepec от Октябрь 23, 2014, 10:02
Не спорю далее я с вами,
Не мне вас в этом убеждать.
Когда нибудь поймёте сами,
Где интуицию вам взять :D


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: vulko от Октябрь 23, 2014, 10:10
Не спорю далее я с вами,
Не мне вас в этом убеждать.
Когда нибудь поймёте сами,
Где интуицию вам взять :D

Так аргументов у тебя просто нет, потому и не споришь.

Слив засчитан. Такой же балабол как игорс, видимо.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 23, 2014, 11:45
Конечно, будьте любезны обьяснить свои фантазии.
Какой-то Вы неумелый провокатор :) Если, по-Вашему, я не разбираюсь, то зачем же Вам слушать мои объяснения? Я могу удобно соскочить: "если Вы такой умный - Вы и объясните". Ну да ладно, попробую поддержать конструктив, но будете еще фыркать - перестану отвечать

Плохо здесь то что классов наворочено много, а толку никакого. Каждый класс посвящен печати - а это 10 раз поменяется в том же проекте. А про повторное использование и говорить нечего. Правильно планировать так чтобы было удобно обработчику который поймал catch и общается с юзверем. Напр чтение/запись файла. Какие случаи меня интересуют?

1) Файл отсутствует
2) открыт по записи др приложением
3) диск переполнен

Все остальное - какая-то "общая ошибка", все равно мне нечего предпринять. От "испустившего" мне нужен КОД ОШИБКИ, а разбираться с ним я буду сам. Напр в случае "3" возможно "Try Again" (юзер почистит trash и нажмет). Зачем было городить класс на каждый случай - хз. Тот клаcc все равно ничего путного не сделает, т.к. он не знает что нужно - это знает "ловящий"


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Akon от Октябрь 23, 2014, 11:57
Observer: по моему скромному опыту могу сказать следующее (сам адепт исключений):
1. Qt их "не всегда" поддерживает, в том смысле, что когда код Qt работает после вашего (например, вы перекрыли виртуальный метод Qt-класса), он не ожидает исключения. Также яркий пример - исключения их слотов (не допустимы).
2. Стоит разделять ошибки программиста и ошибки пользователя. Для первых предназначен assert, для вторых подходят исключения. В этом свете std::logic_error вообще не нужен, если его назначением считать сигнализацию о логической ошибке (ошибке программиста).
3. Тонкая дифференциация ошибки посредством внушительной иерархии классов исключений, конечно, хорошо, но на практике обычно требуется проверка 2-3 вариантов. В подавляющем большинстве случаев в рамках приложения достаточно std::runtime_error c соответствующим сообщением ошибки для пользователя. Если же вы пишете библиотеку, то хорошим тоном будет использование своего класса исключения для специфичных опреаций вашей библиотеки, с тем чтобы иметь возможность отслеживания источника ошибки.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Old от Октябрь 23, 2014, 13:03
Плохо здесь то что классов наворочено много, а толку никакого. Каждый класс посвящен печати - а это 10 раз поменяется в том же проекте. А про повторное использование и говорить нечего.
Это потому, что ТС не смог объяснить для чего же нужны деревья разных исключений, а вы их начали воспринимать в меру своих знаний.

Я уже писал, что C++ это промышленный язык, для разработки больших систем, поэтому когда мы разрабатываем подсистему, мы хотим сделать ее максимально универсальной, что бы было возможно использовать ее без переделки и дублирования во многих местах системы. Поэтому, мы должны учесть и сообщать исключениями о совершенно разных событиях, которые могут произойти при работе. Мы не знаем, где, когда, кто и как эти события будет обрабатывать.
В одном месте системы при использовании подсистемы Storage мне хватит одной общей ошибки StorageError, в другом месте системы, мне понадобиться детально разобраться, а что же происходит с хранилищем и принять меры. Причем, у меня разные исключения могут обрабатываться не то, что в одной функции (или в методах одного класса), а на разных логических уровнях. Какие-то я обработаю в самом низу (нет хранилища - ничего, попробую другое из доступных), какие-то будут обрабатываться уровнем выше (нет данных в хранилище - я попробую обойтись значениями по умолчанию, если получиться), а если хранилища разрушены - то мне ничего не остается, как завершить процесс с выводом диагностики оператору в функции main.

Мне нужны развесистые деревья исключений (да и не только мне), поэтому не стоит рассказывать, что это какие-то ошибки и нормальные программисты так не сделают. Кто тут у нас решает кто нормальный программист, а кто нет?

Тем более не нужно отвечать за всю отрасль, как вы любите говорить: "Обычно делают так". Вы понятия не имеете как это делается на самом деле. Честнее писать "Что так делаете именно вы", ну собственно как все на форуме и делают.



Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: vulko от Октябрь 23, 2014, 15:15
Это потому, что ТС не смог объяснить для чего же нужны деревья разных исключений, а вы их начали воспринимать в меру своих знаний.

...

Мне нужны развесистые деревья исключений (да и не только мне), поэтому не стоит рассказывать, что это какие-то ошибки и нормальные программисты так не сделают. Кто тут у нас решает кто нормальный программист, а кто нет?

Тем более не нужно отвечать за всю отрасль, как вы любите говорить: "Обычно делают так". Вы понятия не имеете как это делается на самом деле. Честнее писать "Что так делаете именно вы", ну собственно как все на форуме и делают.

А зачем объяснять. Достаточно взять для примера жабу.
Наличие четкой и понятной иерархии Exception'ов позволяет понять причину ошибки даже на этапе разработки сразу при её возникновении в 99% случаев без запуска дебага.

Профит.

п.с.
Кстати в соседней ветке "эксперт" игорс предлагал игнорировать мои советы не плодить ненужные классы в низкоуровневом скрытом апи, и всячески поддерживал эту идею.
А тут, где они действительно нужны, совершенно по другому пишет.
Просто фрилансер-недоучка набивает посты...))


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Igors от Октябрь 23, 2014, 19:14
.. а вы их начали воспринимать в меру своих знаний.

...не стоит рассказывать,

..Кто тут у нас решает кто нормальный программист, а кто нет?

.. Вы понятия не имеете как это делается на самом деле.
Ну почему из Вас постоянно прет заносчивость и спесь? Так Вы ничего не достигаете и никого не убеждаете, только себе вредите.

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

Ладно, раз пошел переход на личности - тема себя исчерпала (впрочем и была дохленькая). Умолкаю


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Old от Октябрь 23, 2014, 19:26
Ну почему из Вас постоянно прет заносчивость и спесь?
Нее, спесь прет из вас, если вас не одергивать, вы переходите не то что на личности, на открытые оскорбления.

Так Вы ничего не достигаете и никого не убеждаете, только себе вредите.
Это потому, что мне не нужно здесь ничего достигать. В отличие от вас. :)

Практически это вся аргументация.
Аргументация была выше. Резюмируя, для того, что бы иметь возможность обрабатывать разные ситуации в разных местах, мне нужны отдельные классы для каждой ситуации. Внимание! Не один класс с кодом ситуации, а именно разные классы.

Ладно, раз пошел переход на личности - тема себя исчерпала (впрочем и была дохленькая). Умолкаю
Ну что, хорошо. Я надеюсь и на личности больше переходить не будете. :)



Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 24, 2014, 09:18
Observer: по моему скромному опыту могу сказать следующее (сам адепт исключений):
1. Qt их "не всегда" поддерживает, в том смысле, что когда код Qt работает после вашего (например, вы перекрыли виртуальный метод Qt-класса), он не ожидает исключения. Также яркий пример - исключения их слотов (не допустимы).
Очень важный момент. Хотелось бы не упустить. Давайте не сразу всё, а немного уточним до этого момента: "вы перекрыли виртуальный метод Qt-класса". Что значит: "Qt работает после вашего исключения"? После моего обработанного исключения? Или необработанного? Вроде после необработанного исключения приложение падает. Не всегда? Я думаю, что в стандарте C++ должен быть ответ, но врядли я найду. Прошу прощения, но Qt и C++ я изучаю (по-настоящему и серьёзно) меньше года и сейчас с боем прорываюсь всеми методами, в том числе и провакационными темами и вопросами. На войне лучше всего узнаёшь людей. Кто действительно дело говорит, а кто самоутверждается за счёт унижения других

"Qt работает после вашего отловленного исключения". Я готов предположить, что имеется ввиду ситуация, когда я перебрасываю исключение на уровень выше. Просто уточните: да или нет. Я обязательно напишу пример. Только помогите разобраться или натолкните на мысль

"Также яркий пример - исключения их слотов (не допустимы). "
Если можно, то чуть подробнее

Покажу, какой я сделал класс "Receiver". А вы напишите, хорош ли он с точки зрения обработки ошибок и в какой ситуации он может быть плох. И какие меры предпринять, чтобы убрать эту "плохость", если она есть. У этого класса есть метод "run()", который запускаем приём данных от COM-порта. Если посылка пришла, то высылается сигнал с данными QByteArray. Этот метод "run()" выбрасывает исключение моего типа "PortError":
Код
C++ (Qt)
class PortError : public std::runtime_error
{
public:
   PortError( const std::string &portName ) : std::runtime_error( "" )
   {
       m_message = "Error: unable to open the port \"" +
               portName + "\"";
   }
   // ...
 

Вот таким образом я вызываю метод "run()"
Код
C++ (Qt)
void MainWindow::runReceiver()
{
   try {
       m_receiver->run();
       connect( m_receiver, SIGNAL( signalReceivedData( QByteArray ) ),
                this, SLOT( slotReceivedData( QByteArray ) ) );
   } catch ( const PortError &e ) {
       QString message( e.what() );
       QMessageBox::information( this, tr( "Error" ), message );
       return;
   } catch( ... ) {
       QString message( "Error: unknown exception" );
       QMessageBox::information( this, tr( "Error" ), message );
       return;
   }
}


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 24, 2014, 09:23
Вот класс "Receiver" целиком:

Receiver.h
Код
C++ (Qt)
#ifndef RECEIVER_H
#define RECEIVER_H
 
#include <QObject>
#include <QString>
#include <QSerialPort>
#include <stdexcept>
#include <string>
#include "PortError.h"
 
class Receiver : public QObject {
   Q_OBJECT
public:
 
   Receiver( const QString &portName = QString( "COM2" ),
             QSerialPort::BaudRate baudRate = QSerialPort::Baud9600,
             QSerialPort::DataBits dataBits = QSerialPort::Data8,
             QSerialPort::Parity parity = QSerialPort::NoParity,
             QSerialPort::StopBits stopBits = QSerialPort::OneStop,
             QSerialPort::FlowControl flowControl = QSerialPort::NoFlowControl );
 
   Receiver( const Receiver &receiver );
 
   ~Receiver();
 
   void run( ) throw( PortError );
 
   QString getPortName() const;
   QSerialPort::BaudRate getBaudRate() const;
   QSerialPort::DataBits getDataBist() const;
   QSerialPort::Parity getParity() const;
   QSerialPort::StopBits getStopBits() const;
   QSerialPort::FlowControl getFlowControl() const;
 
signals:
   void signalReceivedData( QByteArray data );
 
private slots:
   void slotReadyRead( );
 
private:
   QSerialPort m_serialPort;
   QString m_portName;
   QSerialPort::BaudRate m_baudRate;
   QSerialPort::DataBits m_dataBits;
   QSerialPort::Parity m_parity;
   QSerialPort::StopBits m_stopBits;
   QSerialPort::FlowControl m_flowControl;
};
 
#endif // RECEIVER_H
 

Receiver.cpp
Код
C++ (Qt)
#include "Receiver.h"
 
Receiver::Receiver( const QString &portName,
                   QSerialPort::BaudRate baudRate,
                   QSerialPort::DataBits dataBits,
                   QSerialPort::Parity parity,
                   QSerialPort::StopBits stopBits,
                   QSerialPort::FlowControl flowControl ) :
   m_portName( portName ),
   m_baudRate( baudRate ),
   m_dataBits( dataBits ),
   m_parity( parity ),
   m_stopBits( stopBits ),
   m_flowControl( flowControl )
{
}
 
Receiver::Receiver( const Receiver &receiver )
{
   this->m_portName = receiver.getPortName();
   this->m_baudRate = receiver.getBaudRate();
   this->m_dataBits = receiver.getDataBist();
   this->m_parity = receiver.getParity();
   this->m_stopBits = receiver.getStopBits();
   this->m_flowControl = receiver.getFlowControl();
}
 
Receiver::~Receiver()
{
   m_serialPort.close();
}
 
void Receiver::run( ) throw( PortError )
{
   m_serialPort.setPortName( m_portName );
 
   if ( !m_serialPort.open( QIODevice::ReadOnly ) ) {
       throw PortError( m_portName.toStdString() );
   }
 
   m_serialPort.setBaudRate( m_baudRate );
   m_serialPort.setDataBits( m_dataBits );
   m_serialPort.setParity( m_parity );
   m_serialPort.setStopBits( m_stopBits );
   m_serialPort.setFlowControl( m_flowControl );
 
   connect( &m_serialPort, SIGNAL( readyRead( ) ),
            this, SLOT( slotReadyRead( ) ) );
}
 
QString Receiver::getPortName() const
{
   return m_portName;
}
 
QSerialPort::BaudRate Receiver::getBaudRate() const
{
   return m_baudRate;
}
 
QSerialPort::DataBits Receiver::getDataBist() const
{
   return m_dataBits;
}
 
QSerialPort::Parity Receiver::getParity() const
{
   return m_parity;
}
 
QSerialPort::StopBits Receiver::getStopBits() const
{
   return m_stopBits;
}
 
QSerialPort::FlowControl Receiver::getFlowControl() const
{
   return m_flowControl;
}
 
void Receiver::slotReadyRead( )
{
   QByteArray data;
   data = m_serialPort.readAll( );
   emit signalReceivedData( data );
}
 

PortError.h
Код
C++ (Qt)
#ifndef PORTERROR_H
#define PORTERROR_H
 
#include <stdexcept>
#include <string>
 
class PortError : public std::runtime_error
{
public:
   PortError( const std::string &portName ) : std::runtime_error( "" )
   {
       m_message = "Error: unable to open the port \"" +
               portName + "\"";
   }
 
   virtual ~PortError() throw()
   {
 
   }
 
   virtual const char *what() const throw()
   {
       return m_message.c_str();
   }
 
   std::string getMessage()
   {
       return m_message;
   }
 
private:
   std::string m_message;
};
 
#endif // PORTERROR_H
 


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 24, 2014, 13:01
Я придумал отличный пример из жизни вокруг которого можно построить статью. Займусь переделкой статьи позже. А пока изложу суть

Задача отправлять данные в COM-порт. COM-порт может быть закрыт. Либо он может быть отрыт, но данные в него нельзя отправить

Класс Sender имеет два метода:
- open() - для открытия порта
- void send( const QByteArray &data ) - для отправки данных в порт

Пользователь класса Sender открывает порт так:
Код
C++ (Qt)
   try {
       m_sender->open();
   } catch( const PortOpenError &e ) {
       QString message( e.what() );
       QMessageBox::critical( this, tr( "Error" ), message );
       return;
   }
 

Метод open выбрасывает исключение PortOpenError с текстом:

(http://i9.pixs.ru/storage/9/7/9/412png_7451163_14410979.png)

Метод send выбрасывает два исключения PortOpenError, PortSendDataError. Второе исключение с текстом: Error: unable to send data to the port "COM7" in the fucntion "Sender::send()" Оба исключения наследуются от базового класса PortError, поэтому пользотелю класса Sender достаточно написать один catch для PortError, а второй catch( ... ) (для всех исключений), на всякий случай:

Код
C++ (Qt)
void MainWindow::on_sendButton_clicked()
{
   QByteArray data = "Hello!";
 
   try {
       m_sender->send( data );
   } catch( const PortError &e ) {
       QString message( e.what() );
       QMessageBox::critical( this, tr( "Error" ), message );
       return;
   } catch( ... ) {
       QMessageBox::critical( this, tr( "Error" ),
                              tr ( "Error: unknown exception." ) );
       return;
   }
}
 

(http://i9.pixs.ru/storage/1/9/5/413png_2177121_14411195.png) (http://pixs.ru/showimage/413png_2177121_14411195.png)

Sender.h
Код
C++ (Qt)
#ifndef SENDER_H
#define SENDER_H
 
#include <QSerialPort>
#include <QString>
#include "PortOpenError.h"
#include "PortSendDataError.h"
 
class Sender
{
public:
   Sender( const QString &portName = QString( "COM1" ),
           QSerialPort::BaudRate baudRate = QSerialPort::Baud9600,
           QSerialPort::DataBits dataBits = QSerialPort::Data8,
           QSerialPort::Parity parity = QSerialPort::NoParity,
           QSerialPort::StopBits stopBits = QSerialPort::OneStop,
           QSerialPort::FlowControl flowControl = QSerialPort::NoFlowControl );
 
   Sender( const Sender &sender );
 
   ~Sender();
 
   void setPortName( const QString &portName );
 
   QString getPortName() const;
   QSerialPort::BaudRate getBaudRate() const;
   QSerialPort::DataBits getDataBits() const;
   QSerialPort::Parity getParity() const;
   QSerialPort::StopBits getStopBits() const;
   QSerialPort::FlowControl getFlowControl() const;
 
   void open() throw( PortOpenError );
 
   void send( const QByteArray &data )
   throw( PortOpenError, PortSendDataError );
 
private:
   QSerialPort m_serialPort;
   QString m_portName;
   QSerialPort::BaudRate m_baudRate;
   QSerialPort::DataBits m_dataBits;
   QSerialPort::Parity m_parity;
   QSerialPort::StopBits m_stopBits;
   QSerialPort::FlowControl m_flowControl;
};
 
#endif // SENDER_H
 

Sender.cpp
Код
C++ (Qt)
#include "Sender.h"
 
Sender::Sender( const QString &portName,
               QSerialPort::BaudRate baudRate,
               QSerialPort::DataBits dataBits,
               QSerialPort::Parity parity,
               QSerialPort::StopBits stopBits,
               QSerialPort::FlowControl flowControl ) :
   m_portName( portName ),
   m_baudRate( baudRate ),
   m_dataBits( dataBits ),
   m_parity( parity ),
   m_stopBits( stopBits ),
   m_flowControl( flowControl )
{
}
 
void Sender::open() throw( PortOpenError )
{
   m_serialPort.setPortName( m_portName );
 
   std::string functionName = "Sender::open()";
 
   // Open the port
   if ( !m_serialPort.open( QIODevice::WriteOnly ) ) {
       throw PortOpenError( m_serialPort.portName().toStdString(),
                            functionName );
   }
 
   // Set settings
   m_serialPort.setBaudRate( m_baudRate );
   m_serialPort.setDataBits( m_dataBits );
   m_serialPort.setParity( m_parity );
   m_serialPort.setStopBits( m_stopBits );
   m_serialPort.setFlowControl( m_flowControl );
}
 
void Sender::send( const QByteArray &data )
throw( PortOpenError, PortSendDataError )
{
   std::string functionName = "Sender::send()";
 
   // Check opened or not
   if ( !m_serialPort.isOpen() ) {
       throw PortOpenError( m_serialPort.portName().toStdString(),
                            functionName );
   }
 
   // Write data to the port
   if ( m_serialPort.write( data ) == -1 ) {
       throw PortSendDataError( m_serialPort.portName().toStdString(),
                                functionName );
   }
}
 
Sender::Sender( const Sender &sender )
{
   this->m_portName = sender.getPortName();
   this->m_baudRate = sender.getBaudRate();
   this->m_dataBits = sender.getDataBits();
   this->m_parity = sender.getParity();
   this->m_stopBits = sender.getStopBits();
   this->m_flowControl = sender.getFlowControl();
}
 
Sender::~Sender()
{
   m_serialPort.close();
}
 
void Sender::setPortName( const QString &portName )
{
   m_portName = portName;
}
 
QString Sender::getPortName() const
{
   return m_portName;
}
 
QSerialPort::BaudRate Sender::getBaudRate() const
{
   return m_baudRate;
}
 
QSerialPort::DataBits Sender::getDataBits() const
{
   return m_dataBits;
}
 
QSerialPort::Parity Sender::getParity() const
{
   return m_parity;
}
 
QSerialPort::StopBits Sender::getStopBits() const
{
   return m_stopBits;
}
 
QSerialPort::FlowControl Sender::getFlowControl() const
{
   return m_flowControl;
}
 

PortOpenError.h
Код
C++ (Qt)
#ifndef PORTOPENERROR_H
#define PORTOPENERROR_H
 
#include "PortError.h"
 
class PortOpenError : public PortError
{
public:
   PortOpenError( const std::string &portName,
                  const std::string &functionName ) :
       PortError( portName, functionName )
   {
       m_message = "Error: unable to open the port \"" +
               m_portName + "\"" + " in the function " +
               "\"" + m_functionName + "\"";
   }
};
 
#endif // PORTOPENERROR_H
 

PortSendDataError.h
Код
C++ (Qt)
#ifndef PORTSENDDATAERROR_H
#define PORTSENDDATAERROR_H
 
#include "PortError.h"
 
class PortSendDataError : public PortError
{
public:
   PortSendDataError( const std::string &portName,
                      const std::string &functionName ) :
       PortError( portName, functionName )
   {
       m_message = "Error: unable to send data to the port \"" +
               m_portName + "\"" + " in the function " +
               "\"" + m_functionName + "\"";
   }
};
 
#endif // PORTSENDDATAERROR_H
 

PortError.h
Код
C++ (Qt)
#ifndef PORTERROR_H
#define PORTERROR_H
 
#include <stdexcept>
#include <string>
 
class PortError : public std::runtime_error
{
public:
   PortError( const std::string &portName,
              const std::string &functionName ) : std::runtime_error( "" ),
       m_portName( portName ),
       m_functionName( functionName )
   {
   }
 
   virtual ~PortError() throw()
   {
   }
 
   virtual const char *what() const throw()
   {
       return m_message.c_str();
   }
 
protected:
   std::string m_message;
   std::string m_portName;
   std::string m_functionName;
};
 
#endif // PORTERROR_H
 


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 24, 2014, 13:06
Я немного работал с Java и C#, там примерно такая же схема


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 28, 2014, 20:55
Интересное мнение по поводу исключений: (http://www.cyberforum.ru/cpp-beginners/thread1286683.html#post6780007)

Цитата:
Во-первых, сделать проверку на ошибку ничуть не проще, если ее надо протащить на неизвестное количество уровней выше. Код возврата надо тащить с уровня на уровень вручную. Исключение тащить никуда не надо - оно само летит.

Во-вторых, что более важно: throw бросает наверх не какой-то жалкий код ошибки, а целый объект пользовательского типа любого размера. В этот объект вы можете запихать любое количество информации, которое будут аккуратно перенесено с самого низу наверх, к обработчику исключения. Пока исключение летит снизу вверх, его можно перехватывать на полпути, дополнять контекстной информацией и посылать дальше вверх.

В-третьих, механизм перехвата исключения позволяет фильтровать исключения по типу брошенного объекта. Т.е. на любом уровне вы можете перехватывать те типы исключений, которые вас интересуют и игнорировать остальные (которые будут спокойно пролетать вверх).

И т.д. и т.п.

А здесь про Qt и исключения: (http://qt-project.org/forums/viewthread/1150)

Цитата:
When Qt was started exceptions were not available for all the compilers that needed to be supported by Qt. Today we are trying to keep the APIs consistent, so modules that have a history of not using exceptions will generally not get new code using exceptions added.
You will notice exceptions are used in some of the new modules of Qt.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: gil9red от Октябрь 28, 2014, 21:07
Интересное мнение по поводу исключений: (http://www.cyberforum.ru/cpp-beginners/thread1286683.html#post6780007)

Цитата:
When Qt was started exceptions were not available for all the compilers that needed to be supported by Qt. Today we are trying to keep the APIs consistent, so modules that have a history of not using exceptions will generally not get new code using exceptions added.
You will notice exceptions are used in some of the new modules of Qt.

А про какие новые модули говорится?


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Октябрь 28, 2014, 21:18
Может быть это: http://qt-project.org/doc/qt-5/qexception.html


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: Akon от Октябрь 31, 2014, 10:08
8Observer8: К сожалению, у меня нет достаточно времени, чтобы вникать в ваши довольно объемные посты и код. ИМХО, лучший путь глубоко изучить Qt (как и любой другой тоолкит) - это изучить исходники. Запустите отладчик и пройдитесь по коду вызова слота, в котором генерируется исключение.


Название: Re: Стратегия обработки ошибок пользователя с помощью собственных классов исключений
Отправлено: 8Observer8 от Ноябрь 06, 2014, 16:28
Цитировать
Запустите отладчик и пройдитесь по коду вызова слота, в котором генерируется исключение
Спасибо, что обратили внимание. Как будет время - посмотрю. Как я понял из ваших слов - применение исключений в слотах что-то нарушает в работе Qt механизма сигнал\слот