Исключительная безопасность действительно важна в Modern C ++.
Уже есть большой вопрос о безопасности исключений Вот.
Так что я не говорю об исключительной безопасности в целом. Я действительно говорю о безопасности исключений с Qt в C ++. Также есть вопрос о Qt Исключительная безопасность при переполнении стека и у нас есть Qt документация.
Прочитав все, что я мог найти о безопасности исключений с помощью Qt, я действительно чувствую, что с помощью Qt очень трудно добиться безопасности исключений. В результате я не собираюсь бросать какие-либо исключения самостоятельно.
Настоящая проблема в std :: bad_alloc:
Мне кажется, что единственный разумный вариант — выйти из приложения. до брошен std :: bad_alloc (я действительно не хочу входить в неопределенную область поведения).
Для этого можно перегрузить оператор new и:
Прежде чем написать этот новый оператор, я был бы очень признателен за отзывы.
Эта проблема давно решена и имеет идиоматическое решение в Qt.
Все слот-звонки в конечном итоге происходят из:
обработчик события, например:
Таймер timeout
сигнал результатов от QTimer
обработка QTimerEvent
,
Вызов слота из очереди QObejct
обработка QMetaCallEvent
,
код, который вы имеете полный контроль, например:
main
или из QThread::run
или из QRunnable::run
,Обработчик события в объекте всегда достигается через QCoreApplication::notify
, Итак, все, что вам нужно сделать, это создать подкласс класса приложения и переопределить метод notify.
Это влияет все вызовы сигнальных слотов, которые исходят от обработчиков событий. В частности:
все сигналы и их непосредственно прилагается игровые автоматы который возник из обработчиков событий
Это добавляет стоимость за событие, не стоимость за сигнал, и не стоимость за слот. Почему разница важна? Многие элементы управления излучают несколько сигналов за одно событие. QPushButton
, реагируя на QMouseEvent
может излучать clicked(bool)
, pressed()
или же released()
, а также toggled(bool)
все из того же события. Несмотря на несколько излучаемых сигналов, notify
был вызван только один раз.
все вызовы слотов в очереди и вызовы методов
Они реализуются путем отправки 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
, Просто справиться с этим не равняется исключению безопасности.
Вам не нужно что-то такое сложное, как перегрузка operator new
, Создать класс ExceptionGuard
чей деструктор проверяет std::uncaught_exception
, Создайте этот объект в каждом слоте с автоматической продолжительностью вне любого блока try-catch. Если есть исключение, которое все еще выходит, вы можете позвонить std::terminate
незадолго до того, как вы вернетесь в Qt.
Большим преимуществом является то, что вы можете разместить его только в слотах, а не в каждом случайном вызове new
, Большим недостатком является то, что вы можете забыть использовать его.
Кстати, звонить не обязательно std::terminate
, Я все еще советую сделать это в ExceptionGuard
потому что он предназначен в качестве последнего средства. Это может сделать специфическую для приложения очистку. Если у вас есть поведение очистки, характерное для слота, лучше сделать это снаружи ExceptionGuard
в обычном catch
блок.
Это хорошая идея ?
Это ненужно и излишне сложно. Есть много проблем с попыткой справиться std::bad_alloc
:
new
ОС просто резервирует часть вашего (огромного, 64-битного) адресного пространства. Это не отображается в памяти намного позже, когда вы пытаетесь использование Это. Если у вас не хватает памяти, то тот это шаг, который не удастся, и ОС не будет сигнализировать об этом, выдавая исключение C ++ (не может, потому что все, что вы пытались сделать, это прочитать или записать адрес памяти). Вместо этого он генерирует нарушение доступа / segfault. Это стандартное поведение в Linux.Вообще говоря, лучший способ справиться с ситуациями нехватки памяти — просто ничего не делать и позволить им закрыть приложение.
Будет ли мой код безопасным для исключений таким образом?
Qt часто звонит new
сам. Я не знаю, используют ли они nothrow
вариант внутри страны, но вам придется изучить это.
Можно ли даже написать код безопасности исключений с помощью Qt?
Да. Вы можете использовать исключения в своем коде, вам просто нужно перехватить их, прежде чем они распространятся через границы сигнала / слота.