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.