Именованная идиома: опасно?
Я прочитал статью о "Идиоме с именованным контуром" в С++: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Loop
Эта идиома позволяет нам писать такие вещи:
named(outer)
for(int i = 0 ; i < rows ; ++i) {
named(inner)
for(int j = 0 ; j < cols ; ++j) {
if(some_condition)
break(outer); // exit the 'outer' loop
}
}
Такие конструкции уже существуют как основная функция на многих языках, например Java.
Согласно статье, он может быть реализован на С++, определяя два злых макроса:
#define named(blockname) goto blockname; \
blockname##_skip: if (0) \
blockname:
#define break(blockname) goto blockname##_skip;
Я знаю, что многие люди хотели бы изгнать использование goto
. Я лично нашел его полезным в очень редких случаях, особенно когда я хотел break
собрать вложенные петли. Эта идиома кажется мне чистым решением для этого, но нормально ли использовать ее в реальном коде?
На странице обсуждения статьи можно прочитать:
"Не делай этого. Ты попадешь в ад"
Итак, мои вопросы: Каковы недостатки использования идиомы именованного цикла? Это опасно? Если да, то почему?
Бонусный вопрос: возможно ли реализовать именованный continue
аналогично? (Я думаю, что это невозможно, используя синтаксис named(...) for(...;...;...) {}
, но кто знает?)
РЕДАКТИРОВАТЬ: Я согласен с вами, переопределение ключевого слова противно. Как насчет использования #define breakLoop()
вместо?
Ответы
Ответ 1
Как указано в комментариях, #defining break
является проблематичным. Предположим, вы используете что-то еще.
Я бы все же утверждал, что это опасно. Это чрезвычайно необычная идиома (для программистов на C++), поэтому они с меньшей вероятностью понимают, и, следовательно, они могут прервать изменения. Учитывая, что есть менее удивительные и, следовательно, менее опасные - способы достижения одного и того же, я бы посоветовал это сделать.
Рассмотрите возможность размещения петель в функции или лямбда. Затем вы можете return
выйти из внешнего цикла. В качестве преимущества вы можете вернуть информацию о преждевременном выходе, что может быть полезно для внешнего кода.
Ответ 2
Я нахожу пару проблем с этим.
Сначала вы определяете макрос с тем же именем, что и одно из зарезервированных слов. Даже если ваш компилятор не справляется с этим, он подвержен ошибкам, а не и (IMO, по крайней мере) опасен.
Во-вторых, я всегда не решаюсь создавать метки программно. Несмотря на то, что ваш компилятор, вероятно, пожалуется, если вы случайно создадите две метки с тем же именем в одной и той же области, сообщение об ошибке, которое оно генерирует, вероятно, не будет легко понято без того, чтобы программист рассекал эти макросы (что частично нарушает цель дополнительной абстракции).
Вероятно, моя основная проблема заключается в том, что макросы представляют что-то, что не похоже на что-либо в синтаксисе обычного языка. Строки named(...)
не заканчиваются точкой с запятой, а за ними следует блок { ... }
. Добавление любого нового синтаксиса открывает дверь для замешательства разработчика и случайного злоупотребления.
В целом, я вроде как идея именованных циклов, но это не та вещь, которую вы хотите создать с помощью макросов. Это механизм, который действительно должен быть обеспечен самим языком. При использовании C или С++ это более безопасно, безопаснее и удобнее обслуживать этикетку, созданную вручную, и goto
. Почти всегда лучше быть явным, чем скрывать, что происходит за макросами.