Есть ли способ не убить приложение Qt, которое выкинуло std :: bad_alloc?

Исключительная безопасность действительно важна в Modern C ++.

Уже есть большой вопрос о безопасности исключений Вот.
Так что я не говорю об исключительной безопасности в целом. Я действительно говорю о безопасности исключений с Qt в C ++. Также есть вопрос о Qt Исключительная безопасность при переполнении стека и у нас есть Qt документация.

Прочитав все, что я мог найти о безопасности исключений с помощью Qt, я действительно чувствую, что с помощью Qt очень трудно добиться безопасности исключений. В результате я не собираюсь бросать какие-либо исключения самостоятельно.

Настоящая проблема в std :: bad_alloc:

  • Документация Qt гласит, что Выдача исключения из слота, вызванного механизмом соединения Qt с сигнальным слотом, считается неопределенным поведением, если только оно не обрабатывается в слоте.
  • Насколько я знаю, любой слот в Qt может генерировать std :: bad_alloc.

Мне кажется, что единственный разумный вариант — выйти из приложения. до брошен std :: bad_alloc (я действительно не хочу входить в неопределенную область поведения).

Для этого можно перегрузить оператор new и:

  • если в потоке GUI происходит ошибка выделения: закройте (убейте) приложение.
  • если в другом потоке происходит ошибка выделения, просто выведите std :: bad_alloc.

Прежде чем написать этот новый оператор, я был бы очень признателен за отзывы.

  1. Это хорошая идея ?
  2. Будет ли мой код безопасным для исключений таким образом?
  3. Можно ли даже написать код безопасности исключений с помощью Qt?

2

Решение

Эта проблема давно решена и имеет идиоматическое решение в Qt.

Все слот-звонки в конечном итоге происходят из:

  • обработчик события, например:

    • Таймер timeout сигнал результатов от QTimer обработка QTimerEvent,

    • Вызов слота из очереди QObejct обработка QMetaCallEvent,

  • код, который вы имеете полный контроль, например:

    • Когда вы излучаете сигнал в реализации mainили из QThread::runили из QRunnable::run,

Обработчик события в объекте всегда достигается через QCoreApplication::notify, Итак, все, что вам нужно сделать, это создать подкласс класса приложения и переопределить метод notify.

Это влияет все вызовы сигнальных слотов, которые исходят от обработчиков событий. В частности:

  1. все сигналы и их непосредственно прилагается игровые автоматы который возник из обработчиков событий

    Это добавляет стоимость за событие, не стоимость за сигнал, и не стоимость за слот. Почему разница важна? Многие элементы управления излучают несколько сигналов за одно событие. QPushButton, реагируя на QMouseEventможет излучать clicked(bool), pressed() или же released(), а также toggled(bool)все из того же события. Несмотря на несколько излучаемых сигналов, notify был вызван только один раз.

  2. все вызовы слотов в очереди и вызовы методов

    Они реализуются путем отправки QMetaCallEvent к объекту получателя. Вызов выполняется QObject::event, Так как доставка событий участвует, notify используется. Стоимость за вызов-вызов (то есть за слот). Эта стоимость может быть легко уменьшена, если желательно (см. Реализацию).

Если вы излучаете сигнал не из обработчика событий — скажем, изнутри вашего main функция, и слот напрямую подключен, тогда этот метод обработки вещей, очевидно, не будет работать, вы должны обернуть излучение сигнала в блок try / catch.

поскольку QCoreApplication::notify вызывается для каждого доставленного события, единственными издержками этого метода являются стоимость блока try / catch и вызов метода базовой реализации. Последний маленький.

Первое можно смягчить, только поместив уведомление на отмеченные объекты. Это должно быть сделано бесплатно для размера объекта и без использования поиска во вспомогательной структуре данных. Любая из этих дополнительных затрат будет превышать стоимость блока try / catch без исключений.

«Знак» должен исходить от самого объекта. Там есть возможность: QObject::d_ptr->unused, Увы, это не так, поскольку этот член не инициализируется в конструкторе объекта, поэтому мы не можем зависеть от обнуления его. Решение, использующее такую ​​метку, потребует небольшого изменения собственно Qt (добавление unused = 0; линия к QObjectPrivate::QObjectPrivate).

Код:

template <typename BaseApp> class SafeNotifyApp : public BaseApp {
bool m_wrapMetaCalls;
public:
SafeNotifyApp(int & argc, char ** argv) :
BaseApp(argc, argv), m_wrapMetaCalls(false) {}
void setWrapMetaCalls(bool w) { m_wrapMetaCalls = w; }
bool doesWrapMetaCalls() const { return m_wrapMetaCalls; }
bool notify(QObject * receiver, QEvent * e) Q_DECL_OVERRIDE {
if (! m_wrapMetaCalls && e->type() == QEvent::MetaCall) {
// This test is presumed to have a lower cost than the try-catch
return BaseApp::notify(receiver, e);
}
try {
return BaseApp::notify(receiver, e);
}
catch (const std::bad_alloc&) {
// do something clever
}
}
};

int main(int argc, char ** argv) {
SafeNotifyApp<QApplication> a(argc, argv);
...
}

Обратите внимание, что я полностью игнорирую, имеет ли смысл в какой-то конкретной ситуации std::bad_alloc, Просто справиться с этим не равняется исключению безопасности.

4

Другие решения

Вам не нужно что-то такое сложное, как перегрузка operator new, Создать класс ExceptionGuard чей деструктор проверяет std::uncaught_exception, Создайте этот объект в каждом слоте с автоматической продолжительностью вне любого блока try-catch. Если есть исключение, которое все еще выходит, вы можете позвонить std::terminate незадолго до того, как вы вернетесь в Qt.

Большим преимуществом является то, что вы можете разместить его только в слотах, а не в каждом случайном вызове new, Большим недостатком является то, что вы можете забыть использовать его.

Кстати, звонить не обязательно std::terminate, Я все еще советую сделать это в ExceptionGuard потому что он предназначен в качестве последнего средства. Это может сделать специфическую для приложения очистку. Если у вас есть поведение очистки, характерное для слота, лучше сделать это снаружи ExceptionGuardв обычном catch блок.

3

Это хорошая идея ?

Это ненужно и излишне сложно. Есть много проблем с попыткой справиться std::bad_alloc:

  • когда его бросают, обычно с этим мало что можно поделать. У тебя не хватает памяти, всего, что ты пытаться сделать может легко снова потерпеть неудачу.
  • во многих средах могут возникать ситуации нехватки памяти без исключения этого исключения. Когда вы звоните new ОС просто резервирует часть вашего (огромного, 64-битного) адресного пространства. Это не отображается в памяти намного позже, когда вы пытаетесь использование Это. Если у вас не хватает памяти, то тот это шаг, который не удастся, и ОС не будет сигнализировать об этом, выдавая исключение C ++ (не может, потому что все, что вы пытались сделать, это прочитать или записать адрес памяти). Вместо этого он генерирует нарушение доступа / segfault. Это стандартное поведение в Linux.
  • это усложняет ситуацию, которая может быть сложно диагностировать и отлаживать. Сохраняйте это простым, чтобы если случается, ваш код не сделает ничего слишком неожиданного, что в конечном итоге скрывает проблему или мешает вам увидеть, что пошло не так.

Вообще говоря, лучший способ справиться с ситуациями нехватки памяти — просто ничего не делать и позволить им закрыть приложение.

Будет ли мой код безопасным для исключений таким образом?

Qt часто звонит new сам. Я не знаю, используют ли они nothrow вариант внутри страны, но вам придется изучить это.

Можно ли даже написать код безопасности исключений с помощью Qt?

Да. Вы можете использовать исключения в своем коде, вам просто нужно перехватить их, прежде чем они распространятся через границы сигнала / слота.

2
По вопросам рекламы [email protected]