Использование goto для чистого выхода из цикла

У меня есть вопрос об использовании оператора goto в C ++. Я понимаю, что эта тема противоречива, и я не заинтересован в каких-либо широких советах или аргументах (я обычно отказываюсь от использования goto). Скорее, у меня есть конкретная ситуация, и я хочу понять, является ли мое решение, использующее оператор goto, хорошим или нет. Я бы не назвал себя новичком в C ++, но и не отнесу себя к программистам профессионального уровня. Часть кода, сгенерировавшая мой вопрос, вращается в бесконечном цикле после запуска. Общий поток потока в псевдокоде выглядит следующим образом:

void ControlLoop::main_loop()
{
InitializeAndCheckHardware(pHardware) //pHardware is a pointer given from outside
//The main loop
while (m_bIsRunning)
{
simulated_time += time_increment; //this will probably be += 0.001 seconds
ReadSensorData();
if (data_is_bad) {
m_bIsRunning = false;
goto loop_end;
}
ApplyFilterToData();
ComputeControllerOutput();
SendOutputToHardware();
ProcessPendingEvents();

while ( GetWallClockTime() < simulated_time ) {}
if ( end_condition_is_satisified ) m_bIsRunning = false;
}
loop_end:
DeInitializeHardware(pHardware);
}

Указатель pHardware передается извне объекта ControlLoop и имеет полиморфный тип, поэтому для меня нет особого смысла использовать RAII и создавать и разрушать сам аппаратный интерфейс внутри main_loop. Я полагаю, что pHardware мог бы создать временный объект, представляющий своего рода «сессию» или «использование» оборудования, которое можно было автоматически очистить при выходе из main_loop, но я не уверен, сделает ли эта идея более понятной кому-либо иначе каково мое намерение. Выход из цикла будет только три: первый — считывание плохих данных с внешнего оборудования; во-вторых, если ProcessPendingEvents () указывает на отмену, инициированную пользователем, что просто приводит к тому, что m_bIsRunning становится ложным; и последний, если конечное условие выполняется в нижней части цикла. Следует также отметить, что main_loop можно запускать и завершать несколько раз в течение срока службы объекта ControlLoop, поэтому он должен завершиться чисто с помощью m_bIsRunning = false после этого.

Кроме того, я понимаю, что мог бы использовать ключевое слово break здесь, но большинство этих вызовов функций псевдокода внутри main_loop на самом деле не инкапсулированы как функции, просто потому, что им нужно было бы либо иметь много аргументов, либо им всем был бы необходим доступ к переменным-членам. На мой взгляд, оба этих случая были бы более запутанными, чем просто оставить main_loop более длинной функцией, а из-за длины большого цикла while оператор вроде goto loop_end кажется, читается понятнее для меня.

Теперь вопрос: не доставит ли вам этого решения неудобства, если вы напишете его в своем собственном коде? Мне кажется, что это немного неправильно, но я никогда раньше не использовал выражение goto в коде C ++ — отсюда мой запрос о помощи от экспертов. Существуют ли другие основные идеи, которые мне не хватает, которые могли бы сделать этот код более понятным?

Благодарю.

6

Решение

С вашим единственным условием, которое вызывает преждевременное прерывание цикла, я бы просто использовал break, Нет необходимости goto это то что break для.

Однако если любой из этих вызовов функций может вызвать исключение или если вам в конечном итоге понадобится несколько breaks Я бы предпочел контейнер в стиле RAII, это именно то, для чего нужны деструкторы. Вы всегда выполняете вызов DeInitializeHardware, так…

// todo: add error checking if needed
class HardwareWrapper {
public:
HardwareWrapper(Hardware *pH)
: _pHardware(pH) {
InitializeAndCheckHardware(_pHardware);
}

~HardwareWrapper() {
DeInitializeHardware(_pHardware);
}

const Hardware *getHardware() const {
return _pHardware;
}

const Hardware *operator->() const {
return _pHardware;
}

const Hardware& operator*() const {
return *_pHardware;
}

private:
Hardware *_pHardware;
// if you don't want to allow copies...
HardwareWrapper(const HardwareWrapper &other);
HardwareWrapper& operator=(const HardwareWrapper &other);
}

// ...

void ControlLoop::main_loop()
{
HardwareWrapper hw(pHardware);
// code
}

Теперь, что бы ни случилось, вы всегда будете звонить DeInitializeHardware когда эта функция вернется.

3

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

Избегать использования goto это довольно солидная вещь в объектно-ориентированной разработке в целом.

В вашем случае, почему бы просто не использовать break выйти из цикла?

while (true)
{
if (condition_is_met)
{
// cleanup
break;
}
}

Что касается вашего вопроса: ваше использование goto сделало бы меня неудобным. Единственная причина, по которой break менее читабельным является ваш доступ к тому, чтобы не быть сильным разработчиком C ++. Любой опытный разработчик языка C-like, break оба будут читать лучше, а также обеспечат более чистое решение, чем goto,

В частности, я просто не согласен с тем, что

if (something)
{
goto loop_end;
}

более читабельно, чем

if (something)
{
break;
}

который буквально говорит то же самое со встроенным синтаксисом.

5

ОБНОВИТЬ

Если ваша основная проблема заключается в том, что цикл while слишком длинный, вы должны стремиться сделать его короче, C ++ — это ОО-язык, а ОО — для разделения вещей на мелкие части и компоненты, даже в целом не-ОО-языке, который мы обычно по-прежнему считаем должен разбить метод / цикл на маленький и сделать его коротким для чтения. Если в цикле 300 строк, независимо от того, что break / goto действительно не экономит ваше время, не так ли?

ОБНОВИТЬ

Я не против идти к но я не буду использовать это здесь, как вы, я предпочитаю просто использовать перерыв, как правило, разработчику, что он видел перерыв там он знает, что означает перейти к концу времени, и с этим m_bIsRunning = false он может легко понять, что это на самом деле выйти из цикла в течение нескольких секунд. да идти к может сэкономить время на секунды, чтобы понять это, но это также может заставить людей нервничать по поводу вашего кода.

Я могу себе представить, что я использую goto, чтобы выйти из двухуровневого цикла:

while(running)
{
...
while(runnning2)
{
if(bad_data)
{
goto loop_end;
}
}
...
}
loop_end:
3

Вместо того, чтобы использовать goto, вы должны использовать break; чтобы избежать петель.

1

Есть несколько альтернатив goto: break, continue а также return в зависимости от ситуации.

Тем не менее, вы должны иметь в виду, что оба break а также continue ограничены в том, что они влияют только на самый внутренний цикл. return с другой стороны, это ограничение не затрагивается.

В общем, если вы используете goto в выход конкретный объем, то вы можете рефакторинг, используя другую функцию и return утверждение вместо. Вероятно, это облегчит чтение кода в качестве бонуса:

// Original
void foo() {
DoSetup();
while (...) {
for (;;) {
if () {
goto X;
}
}
}
label X: DoTearDown();
}

// Refactored
void foo_in() {
while (...) {
for (;;) {
if () {
return;
}
}
}
}

void foo() {
DoSetup();
foo_in();
DoTearDown();
}

Примечание: если ваше тело функции не может удобно разместиться на экране, вы делаете это неправильно.

1

Goto не является хорошей практикой для выхода из цикла, когда break это вариант.

Кроме того, в сложных подпрограммах хорошо иметь только одну логику выхода (с очисткой), размещенную в конце. Goto иногда используется для перехода к логике возврата.

Пример из блочного драйвера QEMU vmdk:

static int vmdk_open(BlockDriverState *bs, int flags)
{
int ret;
BDRVVmdkState *s = bs->opaque;

if (vmdk_open_sparse(bs, bs->file, flags) == 0) {
s->desc_offset = 0x200;
} else {
ret = vmdk_open_desc_file(bs, flags, 0);
if (ret) {
goto fail;
}
}
/* try to open parent images, if exist */
ret = vmdk_parent_open(bs);
if (ret) {
goto fail;
}
s->parent_cid = vmdk_read_cid(bs, 1);
qemu_co_mutex_init(&s->lock);

/* Disable migration when VMDK images are used */
error_set(&s->migration_blocker,
QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
"vmdk", bs->device_name, "live migration");
migrate_add_blocker(s->migration_blocker);

return 0;

fail:
vmdk_free_extents(bs);
return ret;
}
0

Я вижу множество людей, предлагающих break вместо goto, Но break нет «лучше» (или «хуже»), чем goto,

Инквизиция против goto эффективно началось с Дейкстры Бумага «Считать Вредным» еще в 1968 году, когда код спагетти был правилом, и такие вещи, как блочная структура if а также while заявления все еще рассматривались передовой. У ALGOL 60 они были, но по сути это был исследовательский язык, используемый учеными (см. ML сегодня); Фортран, один из доминирующих языков в то время, не получал их еще 9 лет!

Основные моменты в работе Дейкстры:

  1. Люди хорошо разбираются в пространственных рассуждениях, и программы с блочной структурой извлекают из этого выгоду, потому что действия программ, которые происходят рядом друг с другом во времени, описываются рядом друг с другом в «пространстве» (программный код);
  2. Если вы избегаете goto во всех его различных формах, то возможно знать вещи о возможных состояниях переменных в каждой лексической позиции в программе. В частности, в конце while цикл, вы знаете, что условие этого цикла должно быть ложным. Это полезно для отладки. (Дейкстра не совсем так говорит, но вы можете сделать вывод.)

break, как goto (и рано returns и исключения …), уменьшает (1) и исключает (2). Конечно, используя break часто позволяет избежать написания запутанной логики для while условие, что дает вам чистый выигрыш в понимании — и точно так же относится к goto,

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