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

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

Страниц: 1 ... 4 5 [6] 7   Вниз
  Печать  
Автор Тема: [РЕШЕНО] Обработка ошибок без исключений и с помощью исключений  (Прочитано 50577 раз)
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #75 : Март 27, 2014, 15:45 »

Я же имел в виду восстановле ние работоспособности программы Улыбающийся
Появилось немного времени, хочу поделится своими мыслями про исключения и восстановления. Улыбающийся

C++ промышленный язык и сделан для создания больших и сложных систем. Любая большая система состоит из кучи маленьких, в идеале независимых подсистем.
Возьмем для примера одну из таких подсистем, пусть это будет менеджер ресурсов. В задачи этой подсистемы может входить загрузка и распределение ресурсов. Пусть на входе, она будет получать путь к директории с файлами ресурсов.
Код
C++ (Qt)
class ResourceManager
{
public:
   explicit ResourceManager( const std::string &dataDir ) throw( BadDataDir );
};
 
При конструировании менеджера проверяется указанная директория и если там нет данных программы (или вообще нет такой директории), то выбрасывается исключение BadDataDir.

А теперь давайте посмотрим: фатальная ли это ситуация, когда в указанной директории нет файлов с данными? Для менеджера ресурсов несомненно да, его существование бессмысленно с неверным директорием. А для всей системы в целом? Да это ерунда.
Система может искать данные в директории пользователя, в общих директориях, короче, в куче разных мест. И если система начала создавать менеджер ресурсов для одного источника и там не оказалось данных (получили исключение), то она может попробовать создать еще 100500 таких менеджеров, пробуя все известные ей источники.

Код
C++ (Qt)
std::shared_ptr<ResourceManager> Core::initResourceManager()
{
   std::vector< std:string > dataDirs = { "~/mysystem/data", "/usr/share/mysystem/data", "/opt/mysystem/data" };
 
   for( auto dir : dataDirs )
   {
       try
       {
           return std::make_shared<ResourceManager>( dir );
       }
       catch( const BadDataDir & )
       {
             log << "Directory " << dir.c_str() << " does not contain system data."
       }
   }
 
   // Вышли сюда, значит не в одной директории нет данных, сообщаем об этом выше, может кто-то и будет знать что с этим делать...
   throw DataNotFound;
}
 

Поэтому, исключение в программе это вовсе не какая то трагедия, а штатная ситуация, которую можно легко обрабатывать.
« Последнее редактирование: Март 27, 2014, 16:27 от Old » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #76 : Март 27, 2014, 16:28 »

то она может попробовать создать еще 100500 таких менеджеров, пробуя все известные ей источники.
Весьма вероятно что такой класс будет синглтоном. Если же хотите иметь 2 или более менеджера ресурсов - то расскажите порядок загрузки ресурса, какой менеджер должен юзаться?
А смысла в исключениях здесь не вижу никакого - просто проверить существование фолдера/файла и не подавать его в конструктор, а сделать метод напр SetDir
Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #77 : Март 27, 2014, 16:37 »

Весьма вероятно что такой класс будет синглтоном. Если же хотите иметь 2 или более менеджера ресурсов - то расскажите порядок загрузки ресурса, какой менеджер должен юзаться?
Упаси Бог. Никаких синлтонов. Тем более для менеджеров ресурсов.
Я могу иметь 100500 источников с данными и для каждого источника иметь свой менеджер. В одном хранить картинки, в другом звуки. Могу иметь несколько источников с картинками.
Порядок поиска ресурсов может быть любой, хоть случайный. Улыбающийся Или сделать приоритеты для менеджеров и искать сначала в более приоритетных источниках (главный системный и addon'ый). Это детали реализации конкретной системы, причем очень мелкие детали.

просто проверить существование фолдера/файла и не подавать его в конструктор, а сделать метод напр SetDir
Не вижу в этом никакого смысла. Кто то еще должен знать как это проверять, для чего? Пусть этим занимается менеджер ресурсов. Только он должен знать как проверить наличие и целостность данных.
« Последнее редактирование: Март 27, 2014, 16:39 от Old » Записан
OKTA
Гость
« Ответ #78 : Март 27, 2014, 16:40 »

Сегодня наткнулся на ситуацию, когда попытка delete вызывает исключение, но оно не ловится даже если стоит catch(...), а ловится в дебаггере. Почему так?
Записан
Old
Джедай : наставник для всех
*******
Online Online

Сообщений: 4350



Просмотр профиля
« Ответ #79 : Март 27, 2014, 16:49 »

Сегодня наткнулся на ситуацию, когда попытка delete вызывает исключение, но оно не ловится даже если стоит catch(...), а ловится в дебаггере. Почему так?
Скорее всего дебагер имеет ввиду не C++ исключение, а системное. Скорее всего где то бьете память.
Записан
OKTA
Гость
« Ответ #80 : Март 27, 2014, 16:53 »

Да там просто память была заблокирована.
А как же ловить системные исключения?
Записан
Bepec
Гость
« Ответ #81 : Март 27, 2014, 16:54 »

Вызывается системное исключение. Оно ловится __try __еxcept, но зачастую его ловля бессмысленна Веселый Стек бьётся в 50% случаев. 
Записан
OKTA
Гость
« Ответ #82 : Март 27, 2014, 16:58 »

Вызывается системное исключение. Оно ловится __try __еxcept, но зачастую его ловля бессмысленна Веселый Стек бьётся в 50% случаев. 
thanks)
Записан
8Observer8
Гость
« Ответ #83 : Апрель 12, 2014, 08:19 »

В начало темы добавил итог темы (с датой), чтобы если кто-то случай зайдёт, то ему не пришлось бы долго выискивать этот итог.

Итог темы. Добавил: Апрель 12, 2014, 09.10
Долго для себя выбирал стратегию ловли ошибок. Остановился на исключениях. Точнее даже на создании своих классов исключений, которые наследуют стандартные: http://i.pixs.ru/storage/2/3/2/171png_9973242_11655232.png

Настоятельно рекомендую прочитать главу "Chapter 10. Handling Errors" (особенно параграф "Writing Your Own Exception Classes") из книги:
Название: 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

Специально для демонстрации сделал два примера со стратегиями:
- с исключениями (как в книге выше): https://github.com/8Observer8/FiveAndFive
- без исключений: https://github.com/8Observer8/text_file

Стратегию "без исключений" мне подсказал "Igors".

P.S. Здесь проект как тестировать класс с исключениями FiveAndFive (выше). Только надо чтобы папки с проектами FiveAndFive и FiveAndFiveTests лежали в одном каталоге. Этот проект создаётся в Qt 5.2.1 так: File -> New File or Project... -> Other Project -> Qt Unit Test: https://github.com/8Observer8/FiveAndFiveTests
« Последнее редактирование: Апрель 12, 2014, 08:38 от 8Observer8 » Записан
OKTA
Гость
« Ответ #84 : Апрель 14, 2014, 10:16 »

Спасибо, 8Observer8, за информацию!
Я понял причину неприязни многих людей к исключениям и свою в том числе)
Проблема исключений не в том, что лень писать лишний код, а в том, что методология return гораздо проще и понятнее логически, чем исключения. Я ради интереса сейчас намерен включить исключения в свой текущий проект и столкнулся с тем, что вот так просто не перепишешь под исключения имеющийся код, особенно когда хочешь использовать исключения не только как замену Return и удобный вывод ошибок пользователю, но и как средство для возможного решения проблем на месте. Немного ломается привычный подход) Но ничего, сделаю)
Записан
8Observer8
Гость
« Ответ #85 : Апрель 14, 2014, 11:19 »

Рад, что пригодилось! Разведка сработала нормально Улыбающийся

Отмечу ещё такую очень важную особенность, которую я прочувствовал до костей. Если выбрасывать стандартные исключения, то текст вывода будет дублироваться и такой подход очень НЕгибкий, так как ничего кроме текста не выбросишь. Вот пример:
Код
C++ (Qt)
void doSomething1( int a, int b ) throw(std::out_of_range, std::invalid_argument) {
   // ...
   throw ( std::out_of_range( "...out of range..." ) );
   // ...
 
   // ...
   throw ( std::invalid_argument( "...invalid argument..." ) );
   // ...
}
 
void doSomething2( int a, int b ) throw(std::out_of_range, std::invalid_argument) {
   // ...
   throw ( std::out_of_range( "...out of range..." ) );
   // ...
 
   // ...
   throw ( std::invalid_argument( "...invalid argument..." ) );
   // ...
}
 

"std::out_of_range" и "std::invalid_argument" наследуют от std::logic_error, поэтому надо делать свой класс LogicError и наследовать его от std::logic_error. Текст ошибки будет формироваться в пользовательских классах OutOfRange и InvalidArgument, которые наследуют от LogicError. То есть текст ошибки только в одном месте. К примеру, если мы выбрасываем OutOfRange, то можем указать сам аргумент и в какой диапазон он не попал.

Вот вызов программы сложения двух целых чисел. Мы можем отлавливать оба пользовательских исключения (OutOfRange и InvalidArgument) одним блоком catch ( const LogicError &e ):
Код
C++ (Qt)
   int result = 0;
   try {
       result = sum( firstNumber, secondNumber );
   } catch ( const LogicError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Uncaught exception." << std::endl;
       return 1;
   }
 

Реализация:
Код
C++ (Qt)
int sum( int firstNumber, int secondNumber ) throw (OutOfRange) {
   int const beginOfRange = -1000000000;
   int const endOfRange = 1000000000;
 
   if ( (firstNumber < beginOfRange) || (endOfRange < firstNumber) ) {
       throw ( OutOfRange( firstNumber, beginOfRange, endOfRange ));
   }
 
   if ( (secondNumber < beginOfRange) || (endOfRange < secondNumber) ) {
       throw ( OutOfRange( secondNumber, beginOfRange, endOfRange ));
   }
 
   int result = firstNumber + secondNumber;
   return result;
}
 

Я к тому, что обязательно надо писать свои классы исключений, так как они гибкие.

P.S. На всякий случай, в моём примере из предыдущего сообщения (FiveAndFive) - я это реализовал.
« Последнее редактирование: Апрель 14, 2014, 11:21 от 8Observer8 » Записан
8Observer8
Гость
« Ответ #86 : Октябрь 15, 2014, 07:25 »

Использую стратегию из этой книги: http://www.amazon.com/Professional-C-Marc-Gregoire/dp/0470932449

Output
Цитировать
Error: divide by zero in the function func()

main.cpp
Код
C++ (Qt)
#include <iostream>
#include "AnotherError.h"
#include "DivideByZero.h"
 
void func( int a, int b )
throw( DivideByZero, AnotherError );
 
int main()
{
   try {
       func( 28, 0 );
   } catch ( const LogicError &e ) {
       std::cerr << e.what() << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Error: unknown expection" << std::endl;
       return 1;
   }
 
   return 0;
}
 
void func( int a, int b )
throw( DivideByZero, AnotherError )
{
   std::string functionName = "func()";
 
   if ( b == 0 ) {
       throw DivideByZero( functionName );
   }
 
   // ...
 
   if ( a == 5 ) {
       throw AnotherError( functionName );
   }
}

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
 

AnotherError.h
Код
C++ (Qt)
#ifndef ANOTHERERROR_H
#define ANOTHERERROR_H
 
#include <string>
#include "LogicError.h"
 
class AnotherError : public LogicError
{
public:
   AnotherError( const std::string &functionName ) :
       LogicError( functionName )
   {
       m_message = "Error: some error in the "
               "function " + m_functionName;
   }
};
 
#endif // ANOTHERERROR_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
 
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #87 : Октябрь 17, 2014, 09:10 »

Позанудствую - использовать исключения в программах на Qt чревато, так как Qt не только не использует исключения в своём API, но еще и некорректно обрабатывает пользовательские исключения, пролетевшие через кутешный код. К примеру, будет утечка в QVector, если конструктор Т кинет исключение. Писать правильный код с исключениями очень и очень сложно (std контейнеры - тому пример), поэтому, имхо, лучше их не использовать.
В свете с++11 мне очень нравится подход с maybe-типом (ака std::experimental::optional) - когда ф-ия возвращает либо значение, либо пустоту. Этот подход расширяется введением класса Error/Result (T value + bool ok + StringType errorString). В отличие от исключений, нам приходится учитывать, что ф-ия может вернуть ошибку и нет возможности пробросить её наверх, что, кмк, весьма удобно (вот если бы в с++ был жавовский спецификатор throws...). Ну и нет проблемы что у нас что-то пролетело из нижележащего кода, что нас поломало.
Upd: Вернее, возможность пробросить ошибку наверх как раз есть, только это приходится делать явно.
« Последнее редактирование: Октябрь 17, 2014, 09:12 от Авварон » Записан
8Observer8
Гость
« Ответ #88 : Октябрь 17, 2014, 20:23 »

Цитировать
К примеру, будет утечка в QVector, если конструктор Т кинет исключение.
Ничего не понял. А в std::vector может быть аналогичная ситуация? Что такое T?

Цитировать
Писать правильный код с исключениями очень и очень сложно (std контейнеры - тому пример)
Не уверен, что правильно понимаю. То есть std контейнеры неудачны, так как в определённых ситуациях выбрасывают исключения, а Qt контейнеры не выбрасывает? Отсюда следует, что для того чтобы писать правильный код нужно полностью отказаться от std контейнеров? Qt контейнеры на 100 % заменяют std контейнеры?

Допустим есть такой код на std::vector, который позволяет контролировать выход за пределы массива:

Код
C++ (Qt)
#include <iostream>
#include <vector>
#include <stdexcept>
 
int main()
{
   std::vector<int> arr = { 1, 2, 3 };
 
   try {
       std::cout << arr.at( 3 ) << std::endl;
   } catch( std::logic_error &e ) {
       std::cerr << "Error: out of range" << std::endl;
       return 1;
   } catch( ... ) {
       std::cerr << "Error: " << std::endl;
       return 1;
   }
 
   return 0;
}
 

Если я использую этот код в Qt программе, что мне за это будет? Почему я не могу использовать исключения аккуратно? Почему именно "лучше их не использовать"?

Извините за возможно глупые вопросы, но я начал изучать профессионально C++ и Qt только в этом году
« Последнее редактирование: Октябрь 17, 2014, 20:25 от 8Observer8 » Записан
Bepec
Гость
« Ответ #89 : Октябрь 17, 2014, 21:29 »

Qt написан без исключений и без возможности их применения.
Т.е. могут возникнуть десятки и сотни утечек/ошибок в внутренностях Qt.

Собственно надо следовать мудрости Страуструпа - "Всегда писать с исключениями или без них, не смешивая".
Записан
Страниц: 1 ... 4 5 [6] 7   Вверх
  Печать  
 
Перейти в:  


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