do-while (0)을 피하는 더 좋은 방법은 무엇입니까? C ++에서 해킹? … … if(check()) {

코드 흐름이 다음과 같은 경우 :

if(check())
{
  ...
  ...
  if(check())
  {
    ...
    ...
    if(check())
    {
      ...
      ...
    }
  }
}

위의 지저분한 코드 흐름을 피하기 위해 일반적 으로이 해결 방법을 보았습니다.

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

이 해결 방법 / 해킹을 피하여 더 높은 수준 (산업 수준) 코드가되는 더 좋은 방법은 무엇입니까?

즉시 사용 가능한 제안은 환영합니다!



답변

이러한 결정을 기능으로 분리하고 returns 대신을 사용하는 것이 허용되는 관행으로 간주됩니다 break. 이러한 모든 검사는 기능과 동일한 추상화 수준에 해당하지만 상당히 논리적입니다.

예를 들면 다음과 같습니다.

void foo(...)
{
   if (!condition)
   {
      return;
   }
   ...
   if (!other condition)
   {
      return;
   }
   ...
   if (!another condition)
   {
      return;
   }
   ...
   if (!yet another condition)
   {
      return;
   }
   ...
   // Some unconditional stuff       
}


답변

goto적어도 ” goto질문이 무엇이든 관계없이 답이 될 수 없다 ” 는 종교적 믿음에 얽매이지 않은 사람들에게는 실제로 사용하는 것이 정답 일 때가 있습니다.

이 코드의 해킹 사용 do { ... } while(0);를 드레싱의 목적에 대한 gotoA와를 break. 을 (를) 사용하려는 경우 goto공개하십시오. 코드 HARDER를 읽도록 만드는 것은 중요하지 않습니다.

특정 상황은 매우 복잡한 조건의 코드가 많은 경우입니다.

void func()
{
   setup of lots of stuff
   ...
   if (condition)
   {
      ...
      ...
      if (!other condition)
      {
          ...
          if (another condition)
          {
              ...
              if (yet another condition)
              {
                  ...
                  if (...)
                     ...
              }
          }
      }
  ....

  }
  finish up.
}

실제로 그러한 복잡한 논리를 갖지 않으면 코드가 정확하다는 것을 명확하게 할 수 있습니다.

void func()
{
   setup of lots of stuff
   ...
   if (!condition)
   {
      goto finish;
   }
   ...
   ...
   if (other condition)
   {
      goto finish;
   }
   ...
   if (!another condition)
   {
      goto finish;
   }
   ...
   if (!yet another condition)
   {
      goto finish;
   }
   ...
   ....
   if (...)
         ...    // No need to use goto here. 
 finish:
   finish up.
}

편집 : 명확히하기 위해, 나는 결코 goto일반적인 해결책으로 의 사용을 제안하지 않습니다 . 그러나 goto다른 솔루션보다 더 나은 솔루션이있는 경우 가 있습니다.

예를 들어 일부 데이터를 수집하고 있고 테스트되는 여러 조건이 일종의 “이것은 수집되는 데이터의 끝”이라고 상상해보십시오. 위치에 따라 달라지는 “계속 / 종료”마커에 따라 다릅니다. 데이터 스트림에 있습니다.

이제 완료되면 데이터를 파일로 저장해야합니다.

그렇습니다. 합리적인 솔루션을 제공 할 수있는 솔루션이 종종 있지만 항상 그런 것은 아닙니다.


답변

bool변수 와 함께 간단한 연속 패턴을 사용할 수 있습니다 .

bool goOn;
if ((goOn = check0())) {
    ...
}
if (goOn && (goOn = check1())) {
    ...
}
if (goOn && (goOn = check2())) {
    ...
}
if (goOn && (goOn = check3())) {
    ...
}

이 실행 체인은을 checkN반환 하자마자 중지됩니다 false. 작업자의 check...()단락으로 인해 더 이상 전화를 걸지 않습니다 &&. 또한, 최적화 컴파일러는 해당 설정을 인식하는 스마트 충분히 goOnfalse일방 통행로이며, 누락을 삽입 goto end당신을 위해. 그 결과, 코드의 성능은 위의 것과 동일한 것 do/ while(0)만 가독성에 고통스러운 타격하지 않고.


답변

  1. 코드를 별도의 함수 (또는 둘 이상)로 추출하십시오. 점검에 실패하면 기능에서 복귀하십시오.

  2. 주변 코드와 너무 밀접하게 연결되어 있고 커플 링을 줄일 수있는 방법을 찾지 못하면이 블록 뒤의 코드를 확인하십시오. 아마도 함수에서 사용하는 일부 리소스를 정리합니다. RAII 오브젝트를 사용하여 이러한 자원을 관리하십시오 . 그런 다음 각 피지 breakreturn(또는 throw더 적절한 경우) 로 바꾸고 객체의 소멸자를 정리하십시오.

  3. 프로그램 흐름이 (필요하게) 너무 구불 구불하게되어 정말로 필요하다면 goto이상한 변장을주는 대신 사용하십시오.

  4. 맹목적으로 금지하는 코딩 규칙이 goto있고 실제로 프로그램 흐름을 단순화 할 수 없다면 do해킹 으로 위장해야 할 것입니다 .


답변

TLDR : RAII , 트랜잭션 코드 (결과가 이미 설정되어있을 때만 결과를 설정하거나 반환) 및 예외.

긴 대답 :

에서 C 코드의 이런 종류의 가장 좋은 방법은 EXIT / CLEANUP / 추가하는 것입니다 다른 지역 자원의 정리가 발생하고 오류 코드 (있는 경우)이 반환되는 경우, 코드에서 레이블을. 이것은 코드를 자연스럽게 초기화, 계산, 커밋 및 리턴으로 나누기 때문에 모범 사례입니다.

error_code_type c_to_refactor(result_type *r)
{
    error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere
    some_resource r1, r2; // , ...;
    if(error_ok != (result = computation1(&r1))) // Allocates local resources
        goto cleanup;
    if(error_ok != (result = computation2(&r2))) // Allocates local resources
        goto cleanup;
    // ...

    // Commit code: all operations succeeded
    *r = computed_value_n;
cleanup:
    free_resource1(r1);
    free_resource2(r2);
    return result;
}

C, 대부분의 코드베이스에서, if(error_ok != ...그리고 goto코드는 일반적으로 몇 가지 편리한 매크로 (뒤에 숨겨진 RET(computation_result), ENSURE_SUCCESS(computation_result, return_code)등).

C ++ 을 통해 제공 추가 도구를 C :

  • 정리 블록 기능은 RAII로 구현 될 수 있습니다. 즉, 더 이상 전체 cleanup블록이 필요하지 않으며 클라이언트 코드가 초기 리턴 명령문을 추가 할 수 있습니다.

  • 계속할 수 없을 때마다 던져 모든 if(error_ok != ...직통 전화로 변환 합니다.

동등한 C ++ 코드 :

result_type cpp_code()
{
    raii_resource1 r1 = computation1();
    raii_resource2 r2 = computation2();
    // ...
    return computed_value_n;
}

이것은 다음과 같은 이유로 모범 사례입니다.

  • 명시 적입니다 (즉, 오류 처리가 명시 적이 지 않지만 알고리즘의 주요 흐름은 다음과 같습니다)

  • 클라이언트 코드를 작성하는 것은 간단합니다

  • 최소한입니다

  • 간단하다

  • 반복적 인 코드 구성이 없습니다

  • 매크로를 사용하지 않습니다

  • 이상한 do { ... } while(0)구문을 사용하지 않습니다

  • 최소한의 노력으로 재사용 할 수 있습니다 (즉, 호출을 computation2();다른 함수 에 복사하려는 경우 do { ... } while(0)새 코드를 추가 하거나 #definegoto wrapper 매크로 및 정리 레이블을 추가 할 필요가 없습니다. 다른 것).


답변

완전성을 위해 답변을 추가하고 있습니다. 다른 많은 답변은 큰 조건 블록이 별도의 기능으로 나눌 수 있다고 지적했습니다. 그러나 여러 번 지적 했듯이이 접근법은 조건부 코드를 원래 컨텍스트와 분리한다는 것입니다. 이것이 C ++ 11의 언어에 람다가 추가 된 이유 중 하나입니다. 람다 사용은 다른 사람들에 의해 제안되었지만 명확한 샘플은 제공되지 않았습니다. 나는이 답변에 하나를 넣었습니다. 나를 때리는 것은 do { } while(0)여러면 에서 접근 방식 과 매우 유사하다는 느낌입니다. 아마도 여전히 goto위장에 있음을 의미합니다 ….

earlier operations
...
[&]()->void {

    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
}();
later operations


답변

확실히 대답하지만 답변 (완전성을 위해서)

대신에 :

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

당신은 쓸 수 있습니다 :

switch (0) {
case 0:
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

이것은 아직도이다 고토 변장하지만, 적어도 그것은 루프는 더 이상 아니다. 즉, 일부를 계속 점검하지 않아도 매우 신중하게 확인할 필요가 없습니다. 블록의 숨겨진 곳은.

구조는 컴파일러가 최적화하기를 희망 할 정도로 간단합니다.

@jamesdlin이 제안한 것처럼 매크로 뒤에 숨길 수도 있습니다.

#define BLOC switch(0) case 0:

그리고 그것을 사용하십시오

BLOC {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

이는 C 언어 구문이 대괄호 블록이 아니라 스위치 뒤의 명령문을 예상하고 해당 명령문 앞에 케이스 레이블을 넣을 수 있기 때문에 가능합니다. 지금까지 나는 그것을 허용하는 요점을 보지 못했지만이 특별한 경우 스위치를 멋진 매크로 뒤에 숨기는 것이 편리합니다.