IT story

C ++에서 불필요한 중괄호?

hot-time 2020. 5. 22. 08:14
반응형

C ++에서 불필요한 중괄호?


오늘 동료에 대한 코드 검토를 할 때 나는 독특한 것을 보았습니다. 그는 새 코드를 다음과 같이 중괄호로 묶었습니다.

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


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

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

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


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


추가 괄호는 괄호 안에 선언 된 변수의 범위를 정의하는 데 사용됩니다. 변수가 범위를 벗어날 때 소멸자가 호출되도록 수행됩니다. 소멸자에서 다른 사람이 그것을 얻을 수 있도록 뮤텍스 (또는 다른 리소스)를 해제 할 수 있습니다.

내 프로덕션 코드에서 다음과 같이 작성했습니다.

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으로 함수에서 사용할 수 있으며 동시에 추가 괄호를 사용하여 범위를 정의 할 수 있습니다. 이렇게하면 추가 괄호 외부의 코드를 여러 스레드에서 동시에 실행할 수 있지만 괄호 내부의 코드는 한 번정확히 하나의 스레드 로 실행됩니다 .


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

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

특정 (하위) 목적을 달성하는 코드 블록을 분리하는 것입니다. 하나의 문장이 내가 원하는 계산 효과를 얻는 것은 드 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 ;  
}

모든 것을 수행하는 함수를 작성해야한다고 주장 할 수 있습니다. 한 번만 수행하면 함수를 작성하면 구문과 매개 변수가 추가됩니다. 작은 점이 보인다. 이것을 매개 변수가없는 익명 함수로 생각하십시오.

운이 좋으면 편집기에 블록을 숨길 수있는 접기 / 펴기 기능이 있습니다.

나는 항상 이것을한다. 검사해야 할 코드의 경계를 아는 것이 큰 기쁨이며, 해당 덩어리가 내가 원하는 것이 아니라면 어떤 행도 볼 필요가 없다는 것을 아는 것이 좋습니다.


새로운 중괄호 블록 안에 선언 된 변수의 수명이이 블록으로 제한되기 때문일 수 있습니다. 마음에 드는 또 다른 이유는 자주 사용하는 편집기에서 코드 접기를 사용할 수 있기 때문입니다.


이것은 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.
        // ...
    }

}

Practical situations when this is useful are rare and may indicate the code is ripe for refactoring, but the mechanism is there should you ever genuinely need it.

Example 3:

{
    MyRaiiClass guard1 = ...;

    // ...

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

    // ...

} // ~MyRaiiClass for guard1 called.

This can be important for RAII in cases when the need for freeing resources does not naturally "fall" onto boundaries of functions or control structures.


This is really useful when using scoped locks in conjunction with critical sections in multithreaded programming. Your scoped lock initialised in the curly braces (usually the first command) will go out of scope at the end of the end of the block and so other threads will be able to run again.


Everyone else already covered correctly the scoping, RAII etc. possiblities, but since you mention an embedded environment, there is one further potential reason:

Maybe the developer doesn't trust this compiler's register allocation or wants to explicitly control the stack frame size by limiting the number of automatic variables in scope at once.

Here isInit will likely be on the stack:

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

If you take out the curly braces, space for isInit may be reserved in the stack frame even after it could potentially be reused: if there are lots of automatic variables with similarly localized scope, and your stack size is limited, that could be a problem.

Similarly, if your variable is allocated to a register, going out of scope should provide a strong hint that register is now available for re-use. You'd have to look at the assembler generated with and without the braces to figure out if this makes a real difference (and profile it - or watch for stack overflow - to see if this difference really matters).


I think others have covered scoping already, so I'll mention the unnecessary braces might also serve purpose in the development process. For example, suppose you are working on an optimization to an existing function. Toggling the optimization or tracing a bug to a particular sequence of statements is simple for the programmer -- see the comment prior to the braces:

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

This practice is useful in certain contexts like debugging, embedded devices, or personal code.


I agree with "ruakh". If you want a good explanation of the various levels of scope in C, check out this post:

Various Levels of Scope in C Application

In general, the use of "Block scope" is helpful if you want to just use a temporary variable that you don't have to keep track of for the lifetime of the function call. Additionally, some people use it so you can use the same variable name in multiple locations for convenience, though that's not generally a good idea. eg:

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;
}

In this particular example, I have defined returnValue twice, but since it is just at block scope, instead of function scope (ie: function scope would be, for example, declaring returnValue just after int main(void) ), I don't get any compiler errors, as each block is oblivious to the temporary instance of returnValue declared.

I can't say that this is a good idea in general (ie: you probably shouldn't re-use variable names repeatedly from block-to-block), but in general, it saves time and lets you avoid having to manage the value of returnValue across the entire function.

Finally, please note the scope of the variables used in my code sample:

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

So, why to use "unnecessary" curly braces?

  • For "Scoping" purposes (as mentioned above)
  • Making code more readable in a way (pretty much like using #pragma, or defining "sections" that can be visualized)
  • Because you can. Simple as that.

P.S. It's not BAD code; it's 100% valid. So, it's rather a matter of (uncommon) taste.


After viewing the code in the edit, I can say that the unnecessary brackets are probably (in the original coders view) to be 100% clear what will happen during the if/then, even tho it is only one line now, it might be more lines later, and the brackets guarantee you wont make an error.

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

if the above was original, and removing "extras" woudl result in:

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

then, a later modification might look like this:

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

and that, would of course, cause an issue, since now isInit would always be returned, regardless of the if/then.


Objects are automagically destroyed when they go out of scope...


Another example of usage is UI-related classes, especially Qt.

For example, you have some complicated UI and a lot of widgets, each of them got its own spacing, layout and etc. Instead of naming them space1, space2, spaceBetween, layout1, ... you can save yourself from non-descriptive names for variables that exist only in two-three lines of code.

Well, some might say that you should split it in methods, but creating 40 non-reusable methods doesn't look ok - so I decided to just add braces and comments before them, so it looks like logical block. Example:

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

Can't say that's the best practice, but it's good one for legacy code.

Got these problems when a lot of people added their own components to UI and some methods became really massive, but it's not practical to create 40 onetime-usage methods inside class that already messed up.

참고URL : https://stackoverflow.com/questions/9704083/unnecessary-curly-braces-in-c

반응형