Ненужные фигурные скобки в C ++?

187

Делая сегодня обзор кода для коллеги, я увидел странную вещь. Он окружил свой новый код такими фигурными скобками:

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

Каков результат, если таковые имеются, из этого? Что может быть причиной для этого? Откуда эта привычка?

Редактировать:

Исходя из входных данных и некоторых вопросов ниже, я чувствую, что мне нужно добавить некоторые вопросы, хотя я уже отметил ответ.

Среда встроенных устройств. Существует много устаревшего кода C, обернутого в одежду C ++. Есть много разработчиков C ++, ставших C ++.

В этой части кода нет критических разделов. Я видел это только в этой части кода. Здесь не делается никаких больших выделений памяти, только установлены некоторые флаги и некоторые изменения.

Код, заключенный в фигурные скобки, выглядит примерно так:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}

(Не обращайте внимания на код, просто придерживайтесь фигурных скобок ...;)) После фигурных скобок есть еще немного перемешивания, проверки состояния и базовой сигнализации.

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

Из моего POV это кажется довольно странным, и я не думаю, что фигурные скобки должны быть в нашем коде. Во всех ответах я видел несколько хороших примеров того, почему можно заключать код в фигурные скобки, но не следует ли вместо этого разделить код на методы?

iikkoo
источник
99
Каков был ответ вашего коллеги, когда вы спросили его, почему он это сделал?
Грэм Борланд
20
Довольно часто встречается с шаблоном RAII. Краткий обзор: c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
Марцин,
9
Я ненавижу ненужные фигурные скобки
Джекнад
8
Были ли какие-либо объявления во внутреннем блоке?
Кит Томпсон,
15
возможно, он просто хотел легко «свернуть» этот новый раздел в своем редакторе
wim

Ответы:

281

Иногда это приятно, так как дает вам новую область видимости, где вы можете более «аккуратно» объявить новые (автоматические) переменные.

В C++это может быть не так важно , так как вы можете ввести новые переменные в любом месте, но , возможно, привычка из C, где вы не можете сделать это до C99. :)

Так как C++имеет деструкторы, также может быть удобно, чтобы ресурсы (файлы, мьютексы и т. Д.) Автоматически высвобождались при выходе из области видимости, что может сделать вещи чище. Это означает, что вы можете удерживать некоторый общий ресурс в течение более короткого промежутка времени, чем если бы вы взяли его в начале метода.

размотать
источник
37
+1 за явное упоминание новых переменных и старой привычки
арне
46
+1 за использование объема блока, используемого для максимально быстрого освобождения ресурсов
Лев
9
Также легко 'if (0)' блок.
vrdhn
Мои рецензенты кода часто говорят мне, что я пишу слишком короткие функции / методы. Чтобы сделать их счастливыми и сохранить себя счастливыми (т.е. отделить несвязанные проблемы, переменную местность и т. Д.), Я использую только эту технику, разработанную @unwind.
ossandcad
21
@ossandcad, они говорят тебе, что твои методы "слишком короткие"? Это очень сложно сделать. 90% разработчиков (включая меня, вероятно) имеют противоположную проблему.
Бен Ли
169

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

ruakh
источник
14
Конечно, на самом деле этот блок нужно просто превратить в отдельную функцию.
BlueRaja - Дэнни Пфлугхофт
8
Историческая справка. Это методика раннего языка Си, позволяющая создавать локальные временные переменные.
Томас Мэтьюз
12
Я должен сказать - хотя я удовлетворен своим ответом, это действительно не лучший ответ здесь; в лучших ответах явно упоминается RAII, поскольку это главная причина, по которой вы хотите, чтобы деструктор вызывался в определенной точке. Это похоже на случай «самого быстрого оружия на Западе»: я отправил достаточно быстро, чтобы набрать достаточно ранних голосов, чтобы набрать «импульс», чтобы набрать быстрее, чем некоторые лучшие ответы. Не то чтобы я жалуюсь! :-)
ruakh
7
@ BlueRaja-DannyPflughoeft Вы упрощаете. «Поместите это в отдельную функцию» - это не решение каждой проблемы кода. Код в одном из этих блоков может быть тесно связан с окружающим кодом, касаясь нескольких его переменных. Использование функций C, для которых требуются операции с указателями. Кроме того, не каждый фрагмент кода может (или должен быть) использоваться повторно, и иногда сам код может даже не иметь смысла. Я иногда помещаю блоки вокруг своих forзаявлений, чтобы создать недолговечное int i;в C89. Конечно, вы не предлагаете, чтобы каждый forбыл в отдельной функции?
Андерс Шеквист,
101

Дополнительные фигурные скобки используются для определения области действия переменной, объявленной внутри фигурных скобок. Это сделано для того, чтобы деструктор вызывался, когда переменная выходит из области видимости. В деструкторе вы можете освободить мьютекс (или любой другой ресурс), чтобы другие могли его получить.

В моем производственном коде я написал что-то вроде этого:

void f()
{
   //some code - MULTIPLE threads can execute this code at the same time

   {
       scoped_lock lock(mutex); //critical section starts here

       //critical section code
       //EXACTLY ONE thread can execute this code at a time

   } //mutex is automatically released here

  //other code  - MULTIPLE threads can execute this code at the same time
}

Как вы можете видеть, таким образом вы можете использовать scoped_lock функцию и в то же время определять ее область действия с помощью дополнительных фигурных скобок. Это гарантирует, что даже если код вне дополнительных фигурных скобок может быть выполнен несколькими потоками одновременно, код внутри фигурных скобок будет выполняться ровно одним потоком за раз.

Наваз
источник
1
Я думаю, что чище просто иметь: scoped_lock lock (mutex) // код критической секции затем lock.unlock ().
шипение
17
@szielenski: Что делать, если код из критического раздела выдает исключение? Либо мьютекс будет заблокирован навсегда, либо код не будет таким чистым, как вы сказали.
Наваз
4
@Nawaz: подход @ szielenski не оставит мьютекс заблокированным в случае исключений. Он также использует, scoped_lockкоторый будет уничтожен на исключениях. Я обычно предпочитаю также ввести новую область видимости для блокировки, но в некоторых случаях unlockэто очень полезно. Например, чтобы объявить новую локальную переменную в критическом разделе, а затем использовать ее позже. (Я знаю, что опоздал, но только для полноты ...)
Стефан
51

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

Однако есть еще одна веская причина для этого.

Это просто для выделения блока кода, который достигает определенной (под) цели. Редко когда один оператор достигает вычислительного эффекта, который я хочу; обычно это занимает несколько Размещение их в блоке (с комментарием) позволяет мне сказать читателю (часто самому себе позже):

  • Этот кусок имеет последовательную концептуальную цель
  • Вот весь необходимый код
  • И вот комментарий о куске.

например

{  // update the moving average
   i= (i+1) mod ARRAYSIZE;
   sum = sum - A[i];
   A[i] = new_value;
   sum = sum + new_value;
   average = sum / ARRAYSIZE ;  
}

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

Если вам повезет, у вашего редактора будет функция сгиба / раскрытия, которая даже позволит вам скрыть блок.

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

Ира Бакстер
источник
23

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

Arne
источник
17

Это то же самое, что и блок if(или whileт. Д.), Только без него if . Другими словами, вы вводите область действия, не вводя управляющую структуру.

Эта «явная область видимости» обычно полезна в следующих случаях:

  1. Чтобы избежать столкновения имен.
  2. К объему using.
  3. Контролировать, когда называются деструкторы.

Пример 1:

{
    auto my_variable = ... ;
    // ...
}

// ...

{
    auto my_variable = ... ;
    // ...
}

Если my_variableслучается, что это особенно удачное имя для двух разных переменных, которые используются изолированно друг от друга, то явная область видимости позволяет вам не придумывать новое имя просто для того, чтобы избежать конфликта имен.

Это также позволяет избежать my_variableслучайного использования не по назначению.

Пример 2:

namespace N1 { class A { }; }
namespace N2 { class A { }; }

void foo() {

    {
        using namespace N1;
        A a; // N1::A.
        // ...
    }

    {
        using namespace N2;
        A a; // N2::A.
        // ...
    }

}

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

Пример 3:

{
    MyRaiiClass guard1 = ...;

    // ...

    {
        MyRaiiClass guard2 = ...;
        // ...
    } // ~MyRaiiClass for guard2 called.

    // ...

} // ~MyRaiiClass for guard1 called.

Это может быть важно для RAII в случаях, когда потребность в освобождении ресурсов не «естественно» попадает на границы функций или структур управления.

Бранко Димитриевич
источник
15

Это действительно полезно при использовании замков с областью действия в сочетании с критическими секциями в многопоточном программировании. Ваша блокировка с ограничением по объему, инициализированная в фигурных скобках (обычно это первая команда), выйдет из области видимости в конце конца блока, и другие потоки смогут снова работать.

learnvst
источник
14

Все остальные уже правильно рассмотрели возможности определения объема, RAII и т. Д., Но поскольку вы упоминаете встроенную среду, есть еще одна потенциальная причина:

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

Здесь isInit, скорее всего, будет в стеке:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
}

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

Точно так же, если ваша переменная назначена регистру, выход из области видимости должен дать четкую подсказку, что регистр теперь доступен для повторного использования. Вы должны посмотреть на ассемблер, сгенерированный с и без фигурных скобок, чтобы выяснить, действительно ли это имеет значение (и профилировать его - или наблюдать за переполнением стека - чтобы увидеть, действительно ли это различие имеет значение).

Бесполезный
источник
+1 хороший момент, хотя я вполне уверен, что современные компиляторы понимают это правильно без вмешательства. (IIRC - по крайней мере для не встроенных компиляторов - они игнорировали ключевое слово 'register' еще в '99, потому что они всегда могли выполнять работу лучше, чем вы.)
Rup
11

Я думаю, что другие уже рассмотрели область видимости, поэтому я упомяну, что ненужные скобки могут также служить цели в процессе разработки. Например, предположим, что вы работаете над оптимизацией существующей функции. Переключение оптимизации или отслеживание ошибки в определенной последовательности операторов является простым для программиста - см. Комментарий перед фигурными скобками:

// if (false) or if (0) 
{
   //experimental optimization  
}

Эта практика полезна в определенных контекстах, таких как отладка, встроенные устройства или персональный код.

kertronux
источник
10

Я согласен с "руах". Если вы хотите получить хорошее объяснение различных уровней области видимости в C, посмотрите этот пост:

Различные уровни области применения в приложении C

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

int unusedInt = 1;

int main(void) {
  int k;

  for(k = 0; k<10; k++) {
    int returnValue = myFunction(k);
    printf("returnValue (int) is: %d (k=%d)",returnValue,k);
  }

  for(k = 0; k<100; k++) {
    char returnValue = myCharacterFunction(k);
    printf("returnValue (char) is: %c  (k=%d)",returnValue,k);
  }

  return 0;
}

В этом конкретном примере я определил returnValue дважды, но, поскольку он находится только в области видимости блока, а не в области действия функции (т. Е. Область действия функции будет, например, объявлять returnValue сразу после int main (void)), я не получить любые ошибки компилятора, так как каждый блок не обращает внимания на объявленный временный экземпляр returnValue.

Я не могу сказать, что это хорошая идея в целом (то есть: вам, вероятно, не следует повторно использовать имена переменных из блока в блок), но в целом это экономит время и позволяет избежать необходимости управлять значение returnValue для всей функции.

Наконец, обратите внимание на область применения переменных, используемых в моем примере кода:

int:  unusedInt:   File and global scope (if this were a static int, it would only be file scope)
int:  k:           Function scope
int:  returnValue: Block scope
char: returnValue: Block scope
облако
источник
Занятый вопрос, чувак. У меня никогда не было 100 взлетов. Что такого особенного в этом вопросе? Хорошая ссылка. C более ценен, чем C ++.
Wolfpack'08
5

Итак, зачем использовать «ненужные» фигурные скобки?

  • Для целей «Scoping» (как упомянуто выше)
  • Сделать код более читабельным (почти как использование #pragmaили определение «разделов», которые можно визуализировать)
  • Потому что ты можешь. Просто как тот.

PS Это не плохой код; это на 100% действительно. Так что это скорее вопрос (необычного) вкуса.

Dr.Kameleon
источник
5

После просмотра кода в редактировании я могу сказать, что ненужные скобки, вероятно (в исходном представлении кодеров), на 100% ясны, что произойдет во время if / then, даже если сейчас это только одна строка, это может быть несколько строк позже, и скобки гарантируют, что вы не ошибетесь.

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) {
     return isInit;
   }
   return -1;
}

если вышеупомянутое было оригинальным, и удаление "лишних" приводит к:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     return isInit;
   return -1;
}

тогда более поздняя модификация может выглядеть так:

{
   bool isInit;
   (void)isStillInInitMode(&isInit);
   if (isInit) 
     CallSomethingNewHere();
     return isInit;
   return -1;
}

и это, конечно, вызовет проблему, поскольку теперь isInit всегда будет возвращаться независимо от if / then.

nycynik
источник
4

Объекты автоматически уничтожаются, когда они выходят из области видимости ...


источник
2

Другим примером использования являются классы, связанные с пользовательским интерфейсом, особенно Qt.

Например, у вас есть сложный пользовательский интерфейс и множество виджетов, каждый из которых имеет свой собственный интервал, макет и т. Д. Вместо того, чтобы называть их, space1, space2, spaceBetween, layout1, ...вы можете уберечь себя от неописательных имен для переменных, которые существуют только в двух-трех строках. код.

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

// Start video button 
{ 
   <here the code goes> 
}
// Stop video button
{
   <...>
}
// Status label
{
   <...>
}

Не могу сказать, что это лучшая практика, но она хороша для устаревшего кода.

Эти проблемы возникли, когда многие люди добавили свои собственные компоненты в пользовательский интерфейс, а некоторые методы стали действительно массовыми, но нецелесообразно создавать 40 одноразовых методов внутри класса, которые уже испортились.

htzfun
источник