Хорошо, вопрос века :)Можно ли использовать goto в следующей ситуации?
Прежде чем вы что-нибудь скажете или подумаете, позвольте мне сказать вам, что я прочитал пару подобных вопросов по этой теме, но я не нашел ясного решения для своих проблема. Мой случай специфичен и типичен для системных программистов, я думаю.
У меня такая ситуация очень часто. Я ненавижу gotos, не знаю, почему, возможно потому, что все кричат, что это плохо. Но до сих пор я не нашел лучшего решения для моего конкретного сценария, и способ, которым я это делаю в настоящее время, может быть более уродливым, чем использование goto.
Вот мой случай: я использую C++ (Visual C++) для разработки приложений Windows, и довольно часто я использую кучу API в своих подпрограммах. Предположим, что следующая ситуация:
int MyMemberFunction()
{
// Some code... //
if (!SomeApi())
{
// Cleanup code... //
return -1;
}
// Some code... //
if (!SomeOtherApi())
{
// Cleanup code... //
return -2;
}
// Some more code... //
if (!AnotherApi())
{
// Cleanup code... //
return -3;
}
// More code here... //
return 0; // Success
}
Таким образом, после каждого Апи я должен проверить, если это удалось, и прервать свою функцию, если это не так. Для этого я использую целую кучу // Cleanup code... //
, часто почти дублируемую, а затем инструкцию return
. Функция выполняет, скажем, 10 задач (например, использует 10 Apis), и если задача №6 терпит неудачу, я должен очистить ресурсы, созданные предыдущими задачами. Обратите внимание, что очистка должна выполняться самой функцией, поэтому обработка исключений не может быть использована. Кроме того, я не вижу, как много разговорный RAII может помочь мне в этой ситуации.
Единственный способ, о котором я думал, - использовать goto для перехода от всех таких случаев отказа к одной метке очистки, помещенной в конце функции.
Есть ли лучший способ сделать это? Будет ли использование goto считаться плохой практикой в такой ситуации? Что делать тогда? Такая ситуация очень типична для меня (и для системных программистов, подобных мне, я считаю).
P.S .: Ресурсы, которые необходимо очищать, имеют разные типы. Там может быть распределение памяти, различные ручки системных объектов, которые необходимо закрыть и т.д.
UPDATE:
Я думаю, что люди до сих пор не поняли, что я хотел (вероятно, я объясняю плохо). Я думал, что псевдокода должно быть достаточно, но вот пример:
Открываю два файла с помощью CreateFile. Если этот шаг завершился неудачно: я должен очистить дескрипторы уже открытых файлов, если они есть. Я позже прочитаю часть одного файла и напишу в другой.
Я использую SetFilePointer для указания указателя на чтение в первом файле. Если этот шаг завершится неудачно: мне нужно закрыть дескрипторы, открытые предыдущим шагом.
Я использую GetFileSize для получения размера целевого файла. Если api не удается, или размер файла ненормален, я должен выполнить очистку: то же, что и на предыдущем шаге.
Я выделяю буфер указанного размера для чтения из первого файла. Если сбой памяти не удался, я снова должен закрыть дескрипторы файлов.
Мне нужно использовать ReadFile для чтения из первого файла. Если это не удается, я должен: освободить буферную память и закрыть дескрипторы файлов.
Я использую SetFilePointer, чтобы указать указатель записи во втором файле. Если это не удается, необходимо выполнить такую же очистку.
Я должен использовать WriteFile для записи во второй файл. Если это не удается, бла-бла-бла ...
Кроме того, предположим, что я охраняю эту функцию с критической секцией, и после того, как я называю EnterCriticalSection
в начале функции, я должен позвонить LeaveCriticalSection
перед каждым return
заявление.
Обратите внимание, что это очень упрощенный пример. Там может быть больше ресурсов, и нужно сделать больше очистки - в основном то же самое, но иногда немного другое, основанное на том, какой шаг потерпел неудачу. Но давайте поговорим в этом примере: как я могу использовать RAII здесь?
[Как плохо это может быть?] (Http://xkcd.com/292/) (Но серьезно, это неплохо, если он производит * более читаемый код *. Это вызов кишки, который вам нужно сделать.) –
Является ли код очистки общим, или код очистки зависит от того, как далеко вы попадете в эту функцию? – Corbin
Один из способов избежать goto - бросить int, а затем поймать его. – QuentinUK