Непотрібні фігурні брекети в C ++?


187

Сьогодні, роблячи огляд коду для колеги, я побачив особливу річ. Він оточив свій новий код такими фігурними дужками:

Constructor::Constructor()
{
   existing code

   {
      New code: do some new fancy stuff here
   }

   existing code
}

Який результат, якщо такий є, від цього? Що може бути причиною цього? Звідки береться ця звичка?

Редагувати:

Виходячи з наведених нижче даних та деяких питань, я вважаю, що мені потрібно додати деякі запитання, хоча я вже позначив відповідь.

У середовище вбудовані пристрої. Існує багато застарілих кодів C, загорнутих в одяг C ++. Є багато розробників на C ++.

У цій частині коду немає критичних розділів. Я бачив це лише в цій частині коду. Не зроблено великих розподілів пам’яті, лише деякі прапори, які встановлені, і трохи біт.

Код, який оточений фігурними дужками, має щось на зразок:

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

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

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

З мого POV це здається досить дивним, і я не думаю, що фігурні дужки повинні бути в нашому коді. У всіх відповідях я побачив кілька хороших прикладів того, чому можна оточити код фігурними дужками, але чи не слід розділяти код на методи?


99
Якою була відповідь вашого колеги, коли ви запитали його, чому він це зробив?
Грем Борланд

20
Досить поширений з малюнком RAII. Короткий огляд: c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
Marcin

9
Я ненавиджу непотрібні фігурні брекети
jacknad

8
Чи були декларації у внутрішньому блоці?
Кіт Томпсон

15
можливо, він просто хотів легко «скласти» той новий розділ у своєму редакторі
відмовити

Відповіді:


281

Іноді це приємно, оскільки він дає вам нову область, де ви можете більш «чисто» оголошувати нові (автоматичні) змінні.

У C++це може бути не так важливо , тому що ви можете ввести нові змінні в будь-якому місці, але , можливо, звичка з C, де ви не можете зробити це до C99. :)

Оскільки C++деструктори мають деструктори, також може бути зручно, щоб ресурси (файли, мютекси, що завгодно) автоматично вивільнялися під час виходу області, що може зробити речі більш чистими. Це означає, що ви можете утримувати якийсь спільний ресурс на коротший термін, ніж ви б, якби ви захопили його на початку методу.


37
+1 за чітке згадування нових змінних та старої звички
arne

46
+1 за використання області блоку, яка використовується для того, щоб якнайшвидше звільнити ресурси
Лев

9
Також легко "if (0)" блокувати.
vrdhn

Мої рецензенти на коди часто кажуть мені, що я пишу занадто короткі функції / методи. Щоб зробити їх щасливими і щоб я був щасливим (тобто, щоб відокремити непов'язані проблеми, мінливість місцевості і т.д.), я використовую саме цю техніку, розроблену @unwind.
ossandcad

21
@ossandcad, вони кажуть, що ваші методи "занадто короткі"? Це зробити надзвичайно важко. У 90% розробників (я, мабуть, включений) є протилежна проблема.
Бен Лі

169

Однією з можливих цілей є контроль змінної області . А оскільки змінні з автоматичним зберіганням знищуються, коли вони виходять із сфери застосування, це також може викликати винищувач деструктора раніше, ніж це було б інакше.


14
Звичайно, справді цей блок повинен бути просто перетворений на окрему функцію.
BlueRaja - Danny Pflughoeft

8
Історична примітка: Це методика з ранньої мови С, яка дозволила створити локальні тимчасові змінні.
Томас Меттьюз

12
Я мушу сказати - хоча я задоволений своєю відповіддю, це справді не найкраща відповідь; кращі відповіді чітко згадують про RAII, оскільки це головна причина, чому ви хочете викликати деструктор у певний момент. Це виглядає як випадок "найшвидшого пістолета на Заході": я розмістив досить швидко, що отримав достатньо швидких оновлень, що набрав "обертів", щоб отримати швидкі результати, ніж якісь кращі відповіді. Не те, що я скаржуся! :-)
ruakh

7
@ BlueRaja-DannyPflughoeft Ви надто спрощуєтеся. "Поставити його в окрему функцію" не є рішенням кожної проблеми з кодом. Код в одному з цих блоків може бути щільно поєднаний з оточуючим кодом, торкаючись кількох його змінних. Використання функцій C, що вимагає операцій з покажчиком. Крім того, не кожен фрагмент коду може бути (або повинен бути) багаторазовим, і іноді код може навіть не мати сенсу. Я іноді розміщую коло своїх forзаяв, щоб створити короткочасний int i;C89. Невже ти не припускаєш, що кожен forповинен виконувати окрему функцію?
Anders Sjöqvist

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

Однією з причин може бути те, що термін служби будь-яких змінних, оголошених всередині нового блоку фігурних дужок, обмежений цим блоком. Ще одна причина, яка спадає на думку, - це можливість використовувати складання коду в улюбленому редакторі.


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

Це дійсно корисно при використанні широкомасштабних замків у поєднанні з критичними секціями в багатопотоковому програмуванні. Ваш зафіксований замок, ініціалізований у фігурних дужках (зазвичай це перша команда), вийде за межі в кінці кінця блоку, тому інші потоки зможуть запускатися знову.


14

Всі інші вже правильно висвітлювали можливості визначення масштабів, RAII тощо, але оскільки ви згадуєте про вбудоване середовище, є ще одна потенційна причина:

Можливо, розробник не довіряє розподілу реєстру цього компілятора або хоче чітко контролювати розмір кадру стека, обмежуючи кількість автоматичних змінних в області дії одразу.

Тут isInit, швидше за все, буде стек:

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

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

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


+1 хороший момент, хоча я досить впевнений, що сучасні компілятори отримують це право без втручання. (IIRC - принаймні для невбудованих компіляторів - вони проігнорували ключове слово "зареєструвати" ще до '99, тому що вони завжди могли зробити кращу роботу, ніж ви могли.)
Rup

11

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

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

Ця практика корисна в певних контекстах, таких як налагодження, вбудовані пристрої або особистий код.


10

Я згоден з "ruahh". Якщо ви хочете добре пояснити різні рівні сфери застосування на 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

Отже, навіщо використовувати «непотрібні» фігурні брекети?

  • Для цілей "визначення рівня" (як зазначено вище)
  • Зробити код більш читабельним певним чином (майже як використання #pragmaабо визначення "розділів", які можна візуалізувати)
  • Тому що ти можеш. Просто як це.

PS Це не BAD-код; це 100% дійсно. Отже, це скоріше справа (нечастого) смаку.


5

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

{
   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 завжди буде повернуто, незалежно від того, якщо / тоді.


4

Об'єкти автоматично руйнуються, коли вони виходять за межі ...


2

Іншим прикладом використання є класи, пов'язані з інтерфейсом користувача, особливо Qt.

Наприклад, у вас є якийсь - то складний інтерфейс і багато віджетів, кожен з них отримав своє власний Разнос, розташування і т.д. Замість того , щоб називати їх space1, space2, spaceBetween, layout1, ...ви можете врятувати себе від не описових імен змінних , які існують тільки в двох-три лініях код.

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

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

Не можу сказати, що це найкраща практика, але це корисний варіант для застарілого коду.

Отримали ці проблеми, коли багато людей додали власні компоненти до інтерфейсу користувача, а деякі методи стали дійсно масовими, але створити 40 методів одноразового використання всередині класу, які вже зіпсувались, не практично.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.