Использование 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++ - отсюда моя просьба о помощи от экспертов. Есть ли какие-либо другие основные идеи, которые мне не хватает, что сделает ли этот код более ясным?
спасибо.
7 ответов
С вашим одним, сингулярным условием, которое заставляет цикл сломаться рано, я бы просто использовал break
. Не нужен goto
что это break
для.
break
s Я бы предпочел контейнер стиля 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
когда функция возвращает.
избегать использования goto
довольно солидная вещь, чтобы сделать в объектно-ориентированном развитии в целом.
в вашем случае, почему бы просто не использовать break
выход из цикла?
while (true)
{
if (condition_is_met)
{
// cleanup
break;
}
}
что касается вашего вопроса: Вы используете goto
было бы неудобно. Единственная причина, что break
менее читаемым является ваше признание в том, что вы не являетесь сильным разработчиком C++. Для любого опытного разработчика C-подобного языка,break
будет читать лучше, а также обеспечить более чистое решение чем goto
.
в частности, я просто не согласен, что
if (something)
{
goto loop_end;
}
более читаем, чем
if (something)
{
break;
}
, которое буквально говорит то же самое со встроенным синтаксисом.
обновление
Если ваша главная забота-цикл while слишком длинный, то вы должны стремиться сделать его короче, C++-это язык OO, а OO-для разделения вещей на маленькие кусочки и компоненты, даже в общем языке, отличном от OO, мы обычно все еще думаем, что мы должны разбить метод/цикл на маленький и сделать его коротким для чтения. Если в цикле есть 300 строк, независимо от того, break/goto действительно не экономит ваше время, нет это?
обновление
Я не против перейти но я не буду использовать его здесь, как вы, я предпочитаю просто использовать перерыв, в основном разработчику, чтобы он увидел перерыв там он знает, что это означает Гото до конца времени, и с этим m_bIsRunning = false он может легко осознавать, что на самом деле он выходит из цикла в течение нескольких секунд. Да перейти может сэкономить время на секунды, чтобы понять это, но это может также заставляйте людей нервничать из-за вашего кода.
то, что я могу себе представить, что я использую goto, было бы выходом из двухуровневого цикла:
while(running)
{
...
while(runnning2)
{
if(bad_data)
{
goto loop_end;
}
}
...
}
loop_end:
существует несколько альтернатив goto
: break
, continue
и return
в зависимости от ситуации.
однако, вы должны иметь в виду, что оба break
и continue
только в том, что они затрагивают только самый внутренний цикл. return
С другой стороны, это ограничение не влияет.
в общем, если вы используете goto
to выход определенной области, то вы можете рефакторинг с помощью другой функции и заявление. Вполне вероятно, что это облегчит чтение кода в качестве бонуса:
// 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();
}
Примечание: Если ваше тело функции не может приспосабливать удобно на ваш экран, то вы делаете его неправильно.
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;
}
я вижу множество людей, предложив break
вместо goto
. Но!--0--> не "лучше" (или "хуже"), чем goto
.
инквизиция против goto
эффективно началось с Dijkstra's "перейти к считается вредным" бумага еще в 1968 году, когда спагетти-код был правилом и такими вещами, как блочная структура if
и while
заявления все еще рассматривались передовая. ALGOL 60 имел их, но это было по существу исследование язык, используемый учеными (ср. ML сегодня); Fortran, один из доминирующих языков в то время, не получит их еще 9 лет!
основные моменты в статье Дийкстры:
- люди хороши в пространственном мышлении, и блочно-структурированные программы используют это, потому что программные действия, которые происходят рядом друг с другом во времени, описываются рядом друг с другом в "пространстве" (программный код);
- если вы избегаете
goto
во всех его различных формах, то это возможно знаю, что вещи о возможных состояниях переменных в каждой лексической позиции в программе. В частности, в концеwhile
цикл, вы знаете, что условие этого цикла должно быть ложным. Это полезно для отладки. (Дийкстра этого не говорит, но можно сделать вывод.)
break
, Как goto
(и в начале return
S, и исключения...), уменьшает (1) и устраняет (2). Конечно, используя break
часто позволяет избежать написание запутанной логики для while
состояние, получив чистую прибыль в понятности-и точно то же самое относится к goto
.