Ненужные фигурные скобки в C ++? - PullRequest
170 голосов
/ 14 марта 2012

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

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

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

Edit:

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

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

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

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

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

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

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

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

Ответы [ 14 ]

264 голосов
/ 14 марта 2012

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

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

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

167 голосов
/ 14 марта 2012

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

99 голосов
/ 14 марта 2012

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

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

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 в функции и в то же время определять ее область действия, используя дополнительные фигурные скобки.Это гарантирует, что даже если код вне дополнительных фигурных скобок может быть выполнен несколькими потоками одновременно, код внутри фигурных скобок будет выполняться ровно одним потоком за раз.

49 голосов
/ 14 марта 2012

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

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

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

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

, например

{  // 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 голосов
/ 14 марта 2012

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

16 голосов
/ 15 марта 2012

Это то же самое, что и блок 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 в случаях, когда необходимопоскольку освобождение ресурсов не «естественно» попадает на границы функций или структур управления.

14 голосов
/ 14 марта 2012

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

12 голосов
/ 15 марта 2012

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

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

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

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

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

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

11 голосов
/ 15 марта 2012

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

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

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

10 голосов
/ 14 марта 2012

Я согласен с "руах".Если вы хотите получить хорошее объяснение различных уровней области видимости в 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
...