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에서 이것은 다소 이상해 보이며 중괄호가 코드에 있어야한다고 생각하지 않습니다. 왜 중괄호로 코드를 묶을 수 있는지에 대한 모든 대답에서 좋은 예를 보았지만 대신 코드를 메소드로 분리해서는 안됩니까?


99
동료가 왜 그랬는지 물었을 때 동료의 대답은 무엇입니까?
— Graham Borland

20
RAII 패턴과 매우 흔합니다. 빠른 개요 : c2.com/cgi/wiki?ResourceAcquisitionIsInitialization
— Marcin

9
나는 불필요한 중괄호가 싫어
— jacknad

8
내부 블록에 선언이 있습니까?
— Keith Thompson

15
어쩌면 그는 편집장에서 새로운 섹션을 쉽게 '접기'하고 싶었습니다
— wim

답변:


281

새로운 (자동) 변수를보다 "깨끗하게"선언 할 수있는 새로운 범위를 제공하기 때문에 때때로 좋습니다.

여기서는 C++새로운 변수를 어디에서나 도입 할 수 있기 때문에 그렇게 중요하지는 않지만 습관은 CC99까지는 할 수 없었습니다. :)

C++소멸자가 있기 때문에 범위가 종료 될 때 리소스 (파일, 뮤텍스 등)를 자동으로 해제하는 것이 편리 할 수 ​​있습니다. 즉, 메서드를 시작할 때 잡은 것보다 짧은 시간 동안 일부 공유 리소스를 유지할 수 있습니다.


37
새로운 변수와 오래된 습관에 대해 명시 적으로 언급하면 ​​+1
— arne

46
가능한 한 빨리 자원을 확보하는 데 사용되는 블록 범위를 사용하여 +1
— Leo

9
블록을 'if (0)'하는 것도 쉽습니다.
— vrdhn

내 코드 검토자는 종종 너무 짧은 함수 / 메서드를 작성한다고 말합니다. 그들에게 행복을주고 행복을 유지하기 위해 (예 : 관련없는 관심사, 가변적 인 지역성 등을 분리하기 위해) @unwind에 의해 정교화 된이 기술 만 사용합니다.
— ossandcad

21
@ossandcad, 그들은 당신의 방법이 "너무 짧다"고 말합니까? 그것은 매우 어렵습니다. 개발자의 90 % (자체 포함)는 반대의 문제가 있습니다.
— Ben Lee

169

한 가지 가능한 목적은 변수 범위 를 제어하는 것입니다 . 또한 자동 저장 기능이있는 변수는 범위를 벗어나면 소멸되므로 소멸자가 다른 방법보다 먼저 호출 될 수 있습니다.


14
물론 실제로 그 블록은 별도의 기능으로 만들어야합니다.
— BlueRaja-대니 Pflughoeft

8
역사적 메모 : 이것은 초기 임시 C 언어의 기술로 지역 임시 변수를 만들 수있었습니다.
— Thomas Matthews

12
내 대답에 만족하지만, 여기서 가장 좋은 대답은 아닙니다. 그 주된 이유이기 때문에 더 나은 대답은, 명시 적으로 RAII를 언급 하는 이유 는 소멸자가 특정 지점에서 호출 할 것입니다. 이것은 "서양에서 가장 빠른 총"의 경우처럼 보인다. 나는 더 빠른 답변을 얻을 수있을 정도로 빠른 투표를했을만큼 빠른 투표를했다. 내가 불평하는 것은 아닙니다! :-)
— ruakh

7
@ BlueRaja-DannyPflughoeft 당신은 지나치게 단순화하고 있습니다. "별도의 기능으로 넣어 라"는 것이 모든 코드 문제의 해결책은 아닙니다. 이러한 블록 중 하나의 코드는 주변 변수와 밀접하게 연결되어 여러 변수에 영향을 줄 수 있습니다. C 함수를 사용하면 포인터 조작이 필요합니다. 또한 모든 코드 스 니펫이 재사용 가능한 것은 아니며 때로는 코드 자체가 의미가 없을 수도 있습니다. 나는 때때로 C89에서 for단기를 만들기 위해 진술을 둘러싼 블록을 넣었다 int i;. 반드시 모든 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 () 만 있으면 더 깨끗하다고 ​​생각합니다.
— Sizzle

17
@ szielenski : 중요한 섹션의 코드에서 예외가 발생하면 어떻게됩니까? 뮤텍스가 영원히 잠기거나 코드가 더 깔끔 하지 않을 것입니다 .
— Nawaz

4
@Nawaz : @szielenski의 접근 방식은 예외 상황에서 뮤텍스를 잠그지 않습니다. 그는 또한 scoped_lock예외에 의해 파괴 될 것을 사용한다 . 나는 일반적으로 잠금 장치에 새로운 범위를 도입하는 것을 선호하지만 경우에 따라 unlock매우 유용합니다. 예를 들어 중요 섹션 내에 새 로컬 변수를 선언 한 다음 나중에 사용하십시오. (늦어서 알고 있지만 단지 완전성에 대한 ...)
— 스테판

51

다른 사람들이 지적했듯이 새로운 블록은 새로운 범위를 도입하여 주변 코드의 네임 스페이스를 폐기하지 않고 필요 이상으로 리소스를 사용하지 않는 자체 변수로 코드를 작성할 수 있습니다.

그러나 이렇게하는 또 다른 좋은 이유가 있습니다.

특정 (하위) 목적을 달성하는 코드 블록을 분리하는 것입니다. 하나의 문장이 내가 원하는 계산 효과를 얻는 것은 드 rare니다. 보통 몇 개가 걸립니다. 주석으로 블록에 배치하면 독자에게 (나중에 나 자신에게) 말할 수 있습니다.

  • 이 덩어리는 일관된 개념적 목적을 가지고 있습니다.
  • 필요한 모든 코드는 다음과 같습니다.
  • 그리고 청크에 대한 의견이 있습니다.

예 :

{  // 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-내장되지 않은 컴파일러의 경우 최소한 'register'키워드는 '99보다 훨씬 더 오래 무시했기 때문에 항상 더 나은 작업을 수행 할 수 있기 때문에 무시했습니다.)
— Rup

11

나는 다른 사람들이 이미 범위를 다루었다고 생각하므로 불필요한 괄호가 개발 과정에서 목적을 달성 할 수 있다고 언급 할 것입니다. 예를 들어 기존 함수에 대한 최적화 작업을하고 있다고 가정합니다. 프로그래머는 최적화를 실행하거나 특정 순서의 명령문으로 버그를 추적하는 것이 간단합니다. 중괄호 앞에 주석을 참조하십시오.

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

이 방법은 디버깅, 내장 장치 또는 개인 코드와 같은 특정 상황에서 유용합니다.


10

"ruakh"에 동의합니다. 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를 두 번 정의했지만 함수 범위 대신 블록 범위에 있기 때문에 (예 : 함수 범위는 int main (void) 직후에 returnValue를 선언하는 것입니다), 나는하지 않습니다 각 블록이 선언 된 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시각화 또는 사용 가능한 "섹션"정의 와 매우 유사 )
  • 넌 할 수 있으니까. 그렇게 간단합니다.

추신 그것은 나쁜 코드가 아닙니다. 100 % 유효합니다. 따라서 (흔하지 않은) 맛의 문제입니다.


5

편집에서 코드를 본 후 불필요한 괄호가 아마도 (원래 코 더보기에서) if / then 동안 일어날 일을 100 % 명확하게 할 수 있다고 말할 수 있습니다. 나중에 더 많은 행을 표시하고 대괄호는 오류를 일으키지 않습니다.

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

위의 내용이 원본이고 "extras"woudl을 제거하면 다음과 같은 결과가 발생합니다.

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

그런 다음 나중에 수정하면 다음과 같습니다.

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

그리고 is / it에 상관없이 isInit이 항상 반환되므로 문제가 발생할 수 있습니다.


4

물체가 범위를 벗어나면 자동으로 파괴됩니다.


2

사용의 또 다른 예는 UI 관련 클래스, 특히 Qt입니다.

예를 들어, 복잡한 UI와 많은 위젯이 있으며 각각 고유 한 간격, 레이아웃 등을 갖습니다. 이름을 지정하는 대신 space1, space2, spaceBetween, layout1, ...설명이 아닌 이름에서 2 ~ 3 줄로만 존재하는 변수를 구할 수 있습니다. 암호.

글쎄, 일부는 메소드로 분할해야한다고 말할 수 있지만 재사용 할 수없는 40 개의 메소드를 작성하는 것은 좋지 않습니다. 따라서 중괄호와 주석을 추가하기 전에 논리 블록처럼 보입니다. 예:

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

이것이 최선의 방법이라고 말할 수는 없지만 레거시 코드에는 좋습니다.

많은 사람들이 자신의 구성 요소를 UI에 추가하고 일부 메서드가 실제로 커졌을 때 이러한 문제가 발생했지만 이미 엉망인 클래스 내에서 한 번만 사용하는 40 가지 일회용 메서드를 만드는 것은 실용적이지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.