Russian Qt Forum

Программирование => С/C++ => Тема начата: kuzulis от Апрель 02, 2010, 18:26



Название: как правильно прервать конструктор класса?
Отправлено: kuzulis от Апрель 02, 2010, 18:26
Доброго времени.

У меня тут назрела небольшая непонятка: как прервать конструктор класса , если в нем произошла какая-либо ошибка?
т.е. нужно в таком случае сделать так, чтобы конструктор вернул НУЛЬ. ,
например:
Код:
...
//реализация
MyClass::MyClass()
{
    if (не выполняется какое-то условие) {
        тут прервать конструирование и вернуть НУЛЬ!
    }

}
...

//где-то в программе
    MyClass *c = new MyClass();
    //тут проверяем, создался ли объект или нет
    if (!c) {
      //тут завершаем программу
    }

т.е. чем прерывать конструктор?:
1. делать в нём return; ?
2. вызывать деструктор?
3. или как ?





Название: Re: как правильно прервать конструктор класса?
Отправлено: BRE от Апрель 02, 2010, 18:37
т.е. чем прерывать конструктор?:
Конструктор можно прервать исключением.
Если не хочешь/не можешь использовать исключения, то можно использовать внутренний флаг валидности объекта. И проверять его после конструирования.
bool MyClass::isValid() const;


Название: Re: как правильно прервать конструктор класса?
Отправлено: niXman от Апрель 02, 2010, 18:40
в конструкторе, крайне нежелательно писать код, который может выполнится неправильно.


Название: Re: как правильно прервать конструктор класса?
Отправлено: kuzulis от Апрель 02, 2010, 18:46
Цитировать
Если не хочешь/не можешь использовать исключения, то можно использовать внутренний флаг валидности объекта. И проверять его после конструирования.
bool MyClass::isValid() const;

т.е. имеется ввиду что-то вроде:

Код:
MyClass::MyClass()
{
    ....
    if (не выполняется какое-то условие) {
        m_valid = false;
        return;
    }
    ....
}

bool MyClass::isValid() const
{
    return m_valid;
}

//где-то в программе
    MyClass *c = new MyClass();
    //тут проверяем, создался ли объект или нет
    if (!c->isValid()) {
      //тут завершаем программу
    }
...

???

Цитировать
в конструкторе, крайне нежелательно писать код, который может выполнится неправильно.

дык а если я в конструкторе класса создаю приватные объекты для этого класса, то как мне узнать что они создались корректно? если у них нет возможности проверить флаг валитности и т.п. то как быть?


Название: Re: как правильно прервать конструктор класса?
Отправлено: Пантер от Апрель 02, 2010, 18:51
Угу, оно самое.


Название: Re: как правильно прервать конструктор класса?
Отправлено: kuzulis от Апрель 02, 2010, 18:54
дык а если я в конструкторе класса создаю приватные объекты для этого класса, то как мне узнать что они создались корректно? если у них нет возможности проверить флаг валитности и т.п. то как быть?

т.е. если я создаю к примеру в конструкторе MyClass::Myclass():

Код:
   QSocketNotifier *notifier = new QSocketNotifier (this);

то как я узнаю, что notifier  создался правильно? ведь по-любому оператор new вернет указатель на память объекта - НО это не факт, что внутри у себя там объект проинициализировался корректно!


Название: Re: как правильно прервать конструктор класса?
Отправлено: Пантер от Апрель 02, 2010, 18:57
Опиши конкретнее ситуацию. Пример желателен.


Название: Re: как правильно прервать конструктор класса?
Отправлено: kuzulis от Апрель 02, 2010, 19:05
Цитировать
Опиши конкретнее ситуацию. Пример желателен.
пример:

*.h
Цитировать
class Myslass : public QObject
{
    Q_OBJECT
public:
    Myslass(QObject *parent = 0);

private:
    QTimer *timer;

}

*.cpp
Цитировать
Myslass::Myslass(QObject *parent)
    : QObject(parent), timer(0)
{
    timer = new QTimer(this);

    //и вот тут нужно проверить, а валиден ли таймер?
    //ведь у класса QTimer нет методов для проверки его валидности!
    // т.е. объект timer то создастся, но как проверить "чисто гипотетически" будет ли он работать корректно?
   // вдруг  в конструкторе таймера пошло что-то не так??

}

вот чисто "гипотетический пример" , вместо таймера можно что угодно подставить :)


Название: Re: как правильно прервать конструктор класса?
Отправлено: Пантер от Апрель 02, 2010, 19:11
Пересмотри архитектуру, поизучай классы Qt, посмотри, как там сделано. Конкретно, вынеси из конструктора код, который может засбоить или используй флаг, это тебе уже говорили выше.


Название: Re: как правильно прервать конструктор класса?
Отправлено: Igors от Апрель 02, 2010, 19:16
Код:
   QSocketNotifier *notifier = new QSocketNotifier (this);

то как я узнаю, что notifier  создался правильно? ведь по-любому оператор new вернет указатель на память объекта - НО это не факт, что внутри у себя там объект проинициализировался корректно!
Обычно так

Код:
QSocketNotifier::QSocketNotifier( ..)
{
  ...
  if (somethingWrong)
    throw MyException();
  ...
}

// вызывающий
try {
    QSocketNotifier *notifier = new QSocketNotifier (this);
}
catch (MyException &) {
 notifier = 0;
}
catch (...) {
 ...
}
Хотя в Qt это не принято  :)


Название: Re: как правильно прервать конструктор класса?
Отправлено: kuzulis от Апрель 02, 2010, 19:37
ок, спс.. будем разбираться


Название: Re: как правильно прервать конструктор клас&#
Отправлено: SASA от Апрель 03, 2010, 14:01
Пересмотри архитектуру, поизучай классы Qt, посмотри, как там сделано. Конкретно, вынеси из конструктора код, который может засбоить или используй флаг, это тебе уже говорили выше.
Я с опаской отношусь к прирыванию конструтора... Лучше разнести создание обьекта и инициализацию.
Код:
MyClass * m = MyClass();
bool b = m->init();
if (!b)
{
    /// Все плохо
}
...
MyClass::MyClass() {}
bool MyClass::init
{
     timer = new QTimer(this);

    //и вот тут нужно проверить, а валиден ли таймер?
    //ведь у класса QTimer нет методов для проверки его валидности!
    // т.е. объект timer то создастся, но как проверить "чисто гипотетически" будет ли он работать корректно?
   // вдруг  в конструкторе таймера пошло что-то не так??
   if (все плохо) rerurn false;

}


Название: Re: как правильно прервать конструктор класса?
Отправлено: Tonal от Апрель 03, 2010, 15:05
Возбуждение исключения - самый правильный и надёжный способ в С++ прервать конструктор и сообщить что произошла ошибка.
При этом автоматом вызываются деструкторы  членов и базовых классов.
Естественно нужно позаботится об освобождение явно выделенных в конструкторе ресурсов.
Для этого можно модифицировать твой пример 2-мя способами:
 1. Использовать "умный" указатель для поля QTimer *timer.
Любой понравившийся из распространённых:
QPointer<QTimer> timer, std::auto_ptr<QTimer> timer, boost::scoped_ptr<QTimer> timer.
 2. Использовать "умные" указатели только внутри коснтруктора.
Для этого код коснтруктора разбивается на 2 области - реальная инициализация во временные переменные где может случится облом и установка полей, где обломов быть не может:
Код
C++ (Qt)
Myslass::Myslass(QObject *parent)
   : QObject(parent), timer(0), notifier(0)
{
   //Здесь могут происходить обломы
   std::auto_ptr<QTimer> timer = new QTimer(this);
   if (otherSomethingWrong)
       throw MyException();
   std::auto_ptr<QSocketNotifier> notifier = new QSocketNotifier (this);
   ....
   //Здесь обломов уже быть не может
   this->timer = timer.release();
   this->notifier = notifier.release();
}
 
В отличии от всяких флагов валидности, оба подхода гарантируют корректное освобождение ресурсов и защищают от работы с невалидным объектом


Название: Re: как правильно прервать конструктор класса?
Отправлено: SABROG от Апрель 06, 2010, 22:47
QPointer<QTimer> timer, std::auto_ptr<QTimer> timer, boost::scoped_ptr<QTimer> timer
...
В отличии от всяких флагов валидности, оба подхода гарантируют корректное освобождение ресурсов и защищают от работы с невалидным объектом

Вместо QPointer лучше QWeakPointer.
Цитировать
QWeakPointer is also more efficient than QPointer, so it should be preferred in all new code.

Я не согласен с тем, что флаг валидности плохое решение. Если QWeakPointer действительно сможет отлавливать невалидные объекты, то исключения тут даже не нужны, жертвовать переносимостью ради удобства лично я не буду.


Название: Re: как правильно прервать конструктор класса?
Отправлено: Igors от Апрель 07, 2010, 04:41
Я не согласен с тем, что флаг валидности плохое решение. Если QWeakPointer действительно сможет отлавливать невалидные объекты, то исключения тут даже не нужны, жертвовать переносимостью ради удобства лично я не буду.
Не видно проблем переносимости т.к. исключения входят в стандарт языка. По поводу "флага валидности" - может и не плохо но что хорошего в нем мало - это точно. Поймать невалиднось и установить флаг легко, трудно с этим дальше работать. От объекта чего-то хотят, зовут его методы и.т.п. А тут выясняется что этот метод "не имеет должного эффекта" т.к. объект-то невалиден и обеспечить эту ф-циональность не может. А унаследовался от такого объекта - проверки валидности лезут в новые/перекрытые методы. В общем все это начинает неприятно быстро расползаться.


Название: Re: как правильно прервать конструктор класса?
Отправлено: kuzulis от Апрель 07, 2010, 07:43
Цитировать
А унаследовался от такого объекта - проверки валидности лезут в новые/перекрытые методы. В общем все это начинает неприятно быстро расползаться.
дадада.. + пицот


Название: Re: как правильно прервать конструктор класса?
Отправлено: SABROG от Апрель 07, 2010, 09:32
От объекта чего-то хотят, зовут его методы и.т.п. А тут выясняется что этот метод "не имеет должного эффекта" т.к. объект-то невалиден и обеспечить эту ф-циональность не может. А унаследовался от такого объекта - проверки валидности лезут в новые/перекрытые методы. В общем все это начинает неприятно быстро расползаться.
Сомневаюсь, что большинство программистов используют try{} catch() везде где только возможно или даже проверяют коды возвратов где это возможно. Поэтому работа с невалидным объектом обычное дело. К тому же многое зависит от того что понимать под валидностью. Одно дело, когда памяти не хватает под объект и совсем другое, когда например отсутствует файл на диске, который попытались открыть в конструкторе класса. Но даже в последнем случае обычно дублируют функционал в виде метода типа MyClass::setFile().

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

Цитировать
исключения входят в стандарт языка
RTTI тоже входит, только его особо не жалуют. Много чего входит, только оно еще не реализовано или не везде.


Название: Re: как правильно прервать конструктор класса?
Отправлено: SASA от Апрель 07, 2010, 11:26
А есть ли в qt конструкторы, которые выбрасывают исключения?


Название: Re: как правильно прервать конструктор класса?
Отправлено: SABROG от Апрель 07, 2010, 14:23
А есть ли в qt конструкторы, которые выбрасывают исключения?
Только там, где они поддерживаются.

Код
C++ (Qt)
/* These wrap try/catch so we can switch off exceptions later.
 
  Beware - do not use more than one QT_CATCH per QT_TRY, and do not use
  the exception instance in the catch block.
  If you can't live with those constraints, don't use these macros.
  Use the QT_NO_EXCEPTIONS macro to protect your code instead.
*/

 
#ifdef QT_BOOTSTRAPPED
#  define QT_NO_EXCEPTIONS
#endif
#if !defined(QT_NO_EXCEPTIONS) && defined(Q_CC_GNU) && !defined (__EXCEPTIONS) && !defined(Q_MOC_RUN)
#  define QT_NO_EXCEPTIONS
#endif
 
#ifdef QT_NO_EXCEPTIONS
#  define QT_TRY if (true)
#  define QT_CATCH(A) else
#  define QT_THROW(A) qt_noop()
#  define QT_RETHROW qt_noop()
#else
#  define QT_TRY try
#  define QT_CATCH(A) catch (A)
#  define QT_THROW(A) throw A
#  define QT_RETHROW throw
#endif
 
 

Код
C++ (Qt)
QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
   : QObject(*new QWidgetPrivate, 0), QPaintDevice()
{
   QT_TRY {
       d_func()->init(parent, f);
   } QT_CATCH(...) {
       QWidgetExceptionCleaner::cleanup(this, d_func());
       QT_RETHROW;
   }
}
 


Название: Re: как правильно прервать конструктор класса?
Отправлено: niXman от Апрель 07, 2010, 14:27
Цитировать
Сомневаюсь, что большинство программистов используют try{} catch() везде где только возможно или даже проверяют коды возвратов где это возможно.
ужос :o
вам бы на Си писать. или на чем-то подобном.

Цитировать
Поэтому работа с невалидным объектом обычное дело.
снова ужос :o

подавляющее большинство программеров, не используют исключения, т.к. не знают/не понимают в чем их плюсы.
а коды ошибок..что тут понимать, оверхедим/хардкодим кучу проверок условий, 20 ифов, или свичь на все случаи жизни ;D
ппц одним словом.
да, Qt приучает так кодить ;)


Название: Re: как правильно прервать конструктор класса?
Отправлено: Igors от Апрель 07, 2010, 16:21
"Большинство программистов" где? На этом форуме? На Qt? Или "в мировом масштабе"?  :) Я гораздо чаще сталкивался с другой крайностью - неумеренное (на мой взгляд) испускание exception, даже там где спокойно можно обойтись кодом возврата. Все хорошо в меру и спорить не о чем. Сам пробовал оба пути, мое мнение пусть не всегда, но частенько с exception получается значительно короче и чище.


Название: Re: как правильно прервать конструктор класса?
Отправлено: niXman от Апрель 07, 2010, 16:40
Цитировать
"Большинство программистов" где? На этом форуме? На Qt? Или "в мировом масштабе"?
в мировом масштабе. чаще всего, такое наблюдаю у тех программистов, которые учатся программировать, используя фреймворк, типа Qt. А должны, прочтя несколько книжек из классики с++.

Цитировать
Я гораздо чаще сталкивался с другой крайностью - неумеренное (на мой взгляд) испускание exception
try{} catch() - предназначены для обработки !исключительных ситуаций!. да, иногда встречается перебор, но в процентном соотношении, это около 10 процентов от кол-ва оверхеда при проверке результатов/флагов.


Название: Re: как правильно прервать конструктор класса?
Отправлено: Авварон от Апрель 08, 2010, 20:01
niXman
особенно "исключительная" ситуация - файл занят другой программой при открытии его на чтение (c#). Любите эксепшны...


Название: Re: как правильно прервать конструктор класса?
Отправлено: Tonal от Апрель 16, 2010, 08:13
Исключительная ситуация - это та, которую невозможно решить на данном уровне абстракции.
Например, для XML-парсера, невозможность прочитать из потока закрывающий тег - исключительная ситуация.
Т. к. по стандаоту он должен прервать обработку в случае невалидности.

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

Поэтому, в хорошем коде довольно мало мест, где встречается конструкция try...catch.
Их может быть в разы, а то и на порядок меньше чем мест, где бы пришлось проверять флаги завершения и валидности. :)
Код получается компактнее и нагляднее.
Правда с исключениями обязательно нужно использовать технику RAII (http://ru.wikipedia.org/wiki/RAII) иначе уродства типа показанного SABROG-ом (http://www.prog.org.ru/index.php?topic=13051.msg84841#msg84841) и более навороченных не избежать.


Название: Re: как правильно прервать конструктор класса?
Отправлено: Barmaglodd от Апрель 16, 2010, 09:34
Вообще многое от культуры программиста зависит. Я очень долго отучал коллегу от catch(...) везде, где только можно, "Чтобы ошибку не выводило."
В хорошем коде должно быть много мест, где обрабатываются ошибки, не важно как они сообщаются ;)
А ещё объясните, как при использовании RAII обрабатывать исключения и ошибки в деструкторах. Вот с кодами возврата всё ясно, а с исключениями - сразу падение программы :)

PS: Я не против исключений, я за разумный подход в выборе метода сообщения об ошибках :)


Название: Re: как правильно прервать конструктор класса?
Отправлено: Авварон от Апрель 16, 2010, 11:45
то ли я не понял RAII то ли я тупой:)
а) какая связь между количеством исключений и тем, как создаются/освобождаются ресурсы.
б) смысл RAII - в том чтобы, о боже, освобождать ресурс в деструторе? А кто-то так не делает? по-моему знание "паттернов" только мешает жить, они все такие гениальные...


Название: Re: как правильно прервать конструктор класса?
Отправлено: BRE от Апрель 16, 2010, 11:54
то ли я не понял RAII то ли я тупой:)
Одно из свойств RAII это как раз гарантированное освобождение ресурсов при выходе из зоны видимости. Поэтому, если происходит исключение ресурсы гарантированно освободятся.

моему знание "паттернов" только мешает жить, они все такие гениальные...
В паттернах ничего гениального нет, при программировании ты рано или позно придешь к этим приемам сам.
Их достоинство в систематизации этих приемов.


Название: Re: как правильно прервать конструктор класса?
Отправлено: Авварон от Апрель 16, 2010, 12:21
Одно из свойств RAII это как раз гарантированное освобождение ресурсов при выходе из зоны видимости. Поэтому, если происходит исключение ресурсы гарантированно освободятся.
я бы сказал это единственное его свойство:)
в том-то и проблема что я гляжу на каждый новый паттерн и удивляюсь "а чо тут такого гениального"


Название: Re: как правильно прервать конструктор класса?
Отправлено: Barmaglodd от Апрель 16, 2010, 12:33
Никто никому не гарантирует освобождение ресурсов, даже вызов деструктора не всегда гарантируется. ;)


Название: Re: как правильно прервать конструктор класса?
Отправлено: BRE от Апрель 16, 2010, 12:42
Никто никому не гарантирует освобождение ресурсов, даже вызов деструктора не всегда гарантируется. ;)
Скажем так, гарантирует вызов деструктора, а вот с освобождением ресурса... тут от ресурса зависит.  :)


Название: Re: как правильно прервать конструктор класса?
Отправлено: Tonal от Апрель 16, 2010, 12:47
Про деструкторы и исключения - исключения из деструктора вылетать не должно.
Т. е. все действия, которые могут повлечь генерацию исключения нужно делать либо в конструкторе, либо явно вызывая какую-либо функцию.
Например, пусть у нас есть класс для транзакций trans_t. Для удобного использования, он должен иметь явные метоы commit - могущий выкинуть исключение и rollback - не выкидывающий.
В деструкторе транзакция должна проверять была ли она завершена, и если нет, вызывать rollback.

Так же и file_t - должен иметь функцию flush - сбрасывающую буфера на диск и могущую выкинуть исключение и функцию close закрывающую файл без исключений. В деструкторе вызывается именно close если файл всё ещё открыт.

Ну и RAII (http://ru.wikipedia.org/wiki/RAII) - всего лишь удобная аббревиатура ссылающаяся на совершенно конкретный приём/шаблон программирования.
Ничего особо выдающегося или гениального в ней нет - всего лишь систематизация, как и написал BRE