У меня есть код, который выглядит примерно так:
void writeToStream( std::ostream & outputStream )
{
MyXmlWriter xmlWriter{ outputStream };
xmlWriter.addNode();
xmlWriter.addNode();
xmlWriter.close(); // should this be called in `MyXmlWriter` destructor?
}
Функция close записывает некоторые закрывающие теги xml, чтобы файл можно было правильно проанализировать. Конструктор записывает заголовок XML-файла. Можно подумать xmlWriter.close();
код очистки. C ++ часто советует вводить код очистки в деструкторы. Таким образом, вы никогда не забудете правильно очистить. Однако в нашем случае код очистки может сгенерировать. (Представь это file
могут быть включены исключения, запись в файл может произойти сбой.) Поэтому, если close()
функция вызывается в деструкторе, затем она должна быть заключена в блок try-catch, который съедает все сгенерированные исключения:
MyXmlWriter::~MyXmlWriter()
{
try
{
close();
}
catch (...)
{
}
}
Однако в этом случае вызывающая сторона не будет уведомлена о каких-либо ошибках. Функция writeToStream()
может не записать закрывающие теги xml в файл, если вызывающий не узнает об этом. Какова лучшая практика в этой ситуации?
Что ты закрываешь? В общем закрываем файл открытым для записи (std::ostream
, FILE*
или системно-зависимый файловый дескриптор) должен быть выполнен до уничтожения, чтобы вы могли проверять ошибки после выполнения закрытия и сообщать о них. Однако существуют исключения, и, в частности, классы, которые переносят открытый файл, должны обычно закрывать его в своем деструкторе (без проверки ошибок, поскольку вы ничего не можете с ними сделать), чтобы обеспечить надлежащую очистку в случае исключение.
Предположительно, исключение перед закрытием означает, что уже произошла ошибка, и записываемый файл не будет использоваться. Я обычно обернуть вывод в классе с commit
функция. commit
закрывает и проверяет на ошибки. Если деструктор вызывается раньше commit
, он закрывается, не проверяя ошибки, а затем удаляет записываемый файл, так как он, вероятно, не завершен или не может быть использован.
Глотание исключений, как правило, является «худшей практикой», потому что в первую очередь она побеждает цель броска.
Но в этом случае вам действительно нужно только подмножество функциональности в деструкторе, не включая сброс, который является «бонусом», но влечет за собой возможность броска. Попытки сброса могут по-прежнему иметь побочные эффекты, такие как ненужное ожидание тайм-аута сети, когда он уже произошел.
Как упоминал Джеймс Канз, лучшая практика заключается в ручной очистке перед запуском деструктора, что исключает исключительное состояние деструктора.
В будущем C ++ может лучше поддерживать транзакции. Но пока ваш подход разумен. В любом случае, это как деструктор std::filebuf
указано на работу:
Последствия: Уничтожает объект класса
basic_filebuf<charT,traits>
, Вызовыclose()
, Если исключение происходит во время уничтожения объекта, включая вызовclose()
исключение поймано, но не переброшено (см. 17.6.5.12).