Programing

GOTO를 사용 하시겠습니까?

lottogame 2020. 11. 29. 09:32
반응형

GOTO를 사용 하시겠습니까?


현재 저는 goto 문이 많이 사용되는 프로젝트를 진행하고 있습니다. goto 문의 주요 목적은 여러 return 문이 아닌 루틴에 하나의 정리 섹션을 갖는 것입니다. 아래와 같이 :

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p = NULL;

   p = new int;
   if (p == NULL)
   {
     cout<<" OOM \n";
     goto Exit;
   }

   // Lot of code...

Exit:
   if(p)
   {
     delete p;
     p = NULL;
   }
   return bRetVal;
}

이렇게하면 코드의 한 섹션, 즉 Exit 레이블 뒤에서 정리 코드를 추적 할 수 있으므로 훨씬 쉽게 정리할 수 있습니다.

그러나 goto 문을 사용하는 것이 나쁜 습관을 여러 곳에서 읽었습니다.

현재 저는 Code Complete 책을 읽고 있는데 그 선언에 가까운 변수를 사용해야한다고 말합니다. goto를 사용하는 경우 goto를 처음 사용하기 전에 모든 변수를 선언 / 초기화해야합니다. 그렇지 않으면 컴파일러에서 goto 문이 xx 변수의 초기화를 건너 뛰는 오류를 발생시킵니다.

어느 쪽이 옳습니까?


Scott의 의견에서 :

goto를 사용하여 한 섹션에서 다른 섹션으로 이동하는 것은 코드를 읽고 이해하기 어렵게 만들기 때문에 좋지 않은 것처럼 보입니다.

하지만 goto를 사용하여 앞으로 나아가고 하나의 레이블로 이동하면 괜찮을 것입니다 (?).


코드 정리가 무엇을 의미하는지 잘 모르겠지만 C ++에는 " 리소스 획득은 초기화입니다 " 라는 개념 이 있으며 소멸자가 항목을 정리해야합니다.

(C # 및 Java에서는 일반적으로 try / finally로 해결됩니다.)

자세한 내용은이 페이지를 확인하세요. http://www.research.att.com/~bs/bs_faq2.html#finally

편집 : 이것을 조금 정리하겠습니다.

다음 코드를 고려하십시오.

void MyMethod()
{
    MyClass *myInstance = new MyClass("myParameter");
    /* Your code here */
    delete myInstance;
}

문제 : 함수에서 여러 개의 이탈이 있으면 어떻게됩니까? 각 출구를 추적하고 가능한 모든 출구에서 개체를 삭제해야합니다! 그렇지 않으면 메모리 누수와 좀비 리소스가 발생합니다.

해결 방법 : 컨트롤이 범위를 벗어날 때 자동으로 정리되므로 개체 참조를 대신 사용하십시오.

void MyMethod()
{
    MyClass myInstance("myParameter");
    /* Your code here */
    /* You don't need delete - myInstance will be destructed and deleted
     * automatically on function exit */
}

예, std::unique_ptr위의 예가 분명히 불완전하기 때문에 또는 비슷한 것을 사용 하십시오.


C ++에서 goto를 사용할 필요가 없었습니다. 이제까지. 이제까지. 사용해야하는 상황이 있으면 매우 드뭅니다. 실제로 goto를 논리의 표준 부분으로 만드는 것을 고려하고 있다면, 무언가가 궤도를 벗어났습니다.


사람들이 gotos와 코드와 관련하여 기본적으로 두 가지 요점을 제시합니다.

  1. 고토는 나쁘다. 고토가 필요한 곳을 만나는 것은 매우 드뭅니다.하지만 완전히 치는 것은 권하지 않습니다. C ++에는 goto가 거의 적합하지 않을만큼 똑똑한 제어 흐름이 있습니다.

  2. 정리 메커니즘이 잘못되었습니다. 이 점이 훨씬 더 중요합니다. C에서 메모리 관리를 직접 사용하는 것은 괜찮을뿐만 아니라 작업을 수행하는 가장 좋은 방법입니다. C ++에서 목표는 가능한 한 메모리 관리를 피하는 것입니다. 가능한 한 메모리 관리를 피해야합니다. 컴파일러에게 맡기십시오. 을 사용하는 대신 new변수를 선언하십시오. 실제로 메모리 관리가 필요한 유일한 경우는 데이터의 크기를 미리 알지 못하는 경우입니다. 그럼에도 불구하고 일부 STL컬렉션을 대신 사용해야 합니다.

합법적으로 메모리 관리가 필요한 경우 (실제로 이에 대한 증거를 제공하지 않은 경우) 생성자를 통해 클래스 내에서 메모리 관리를 캡슐화하여 메모리를 할당하고 해체자를 메모리 할당을 해제해야합니다.

당신의 방식이 훨씬 쉽다는 당신의 반응은 장기적으로는 사실이 아닙니다. 첫째, C ++에 대한 강한 느낌을 받으면 이러한 생성자가 2 차 특성이 될 것입니다. 개인적으로 정리 코드를 사용하는 것보다 생성자를 사용하는 것이 더 쉽다는 것을 알았습니다. 제대로 할당을 해제하기 위해주의를 기울일 필요가 없기 때문입니다. 대신 객체가 범위를 벗어나게하면 언어가 대신 처리합니다. 또한 유지 관리는 정리 섹션을 유지하는 것보다 훨씬 쉽고 문제 발생 가능성이 훨씬 적습니다.

요컨대, goto일부 상황에서는 좋은 선택이 될 수 있지만 이번에는 그렇지 않습니다. 여기서 그것은 단지 단기적인 게으름입니다.


귀하의 코드는 매우 비관 상적이며 절대 작성해서는 안됩니다. 기본적으로 C ++에서 C를 에뮬레이트하고 있습니다. 그러나 다른 사람들은 그것에 대해 언급하고 대안으로 RAII를 지적했습니다.

그러나 다음과 같은 이유로 코드 예상대로 작동하지 않습니다 .

p = new int;
if(p==NULL) { … }

( 이상하게 과부하가 걸린 경우를 제외하고) 절대로 평가 되지 않습니다 . 경우 충분한 메모리를 할당 할 수 없습니다, 그것은 예외가 발생, 그것은 결코 , 이제까지 반환 , 매개 변수의 세트 이상하지에서; 유형의 인스턴스를 취하고 실제로 예외를 던지는 대신 반환 하는 특별한 배치-새 오버로드가 있습니다. 그러나이 버전은 일반 코드에서 거의 사용되지 않습니다. 일부 저수준 코드 또는 임베디드 장치 응용 프로그램은 예외 처리 비용이 너무 많이 드는 상황에서 이점을 얻을 수 있습니다.trueoperator newoperator new0std::nothrow0

비슷한는 마찬가지입니다 delete: 말했듯이 랄드으로, 블록 if (p)앞에 불필요하다 delete p.

또한이 코드를 다음과 같이 다시 작성할 수 있기 때문에 예제가 의도적으로 선택되었는지 확실하지 않습니다.

bool foo() // prefer native types to BOOL, if possible
{
    bool ret = false;
    int i;
    // Lots of code.
    return ret;
}

아마 좋은 생각이 아닐 것 입니다.


일반적으로 표면적으로는 레이블이 하나만 있고 항상 앞으로 나아갈 수 있다면 접근 방식에 문제가 없습니다. 예를 들어 다음 코드는 다음과 같습니다.

int foo()
{
    int *pWhatEver = ...;
    if (something(pWhatEver))
    { 
        delete pWhatEver;
        return 1;
    }
    else
    {
        delete pWhatEver;
        return 5;
    }
}

그리고이 코드 :

int foo()
{
    int ret;
    int *pWhatEver = ...;
    if (something(pWhatEver))
    { 
        ret = 1;
        goto exit;
    }
    else
    {
        ret = 1;
        goto exit;
    }
exit:
    delete pWhatEver;
    return ret;
}

정말 서로 다르지 않습니다. 하나를 받아 들일 수 있다면 다른 하나도 받아 들일 수 있어야합니다.

그러나 많은 경우에 RAII (리소스 획득은 초기화) 패턴을 사용하면 코드를 훨씬 더 깨끗하고 유지 관리 할 수 ​​있습니다. 예를 들어 다음 코드는 다음과 같습니다.

int foo()
{
    Auto<int> pWhatEver = ...;

    if (something(pWhatEver))
    {
        return 1;
    }
    else
    {
        return 5;
    }
}

이전 예제보다 짧고 읽기 쉬우 며 유지 관리하기 쉽습니다.

따라서 가능한 경우 RAII 접근 방식을 사용하는 것이 좋습니다.


귀하의 예는 예외적으로 안전하지 않습니다.

goto를 사용하여 코드를 정리하는 경우 정리 코드 전에 예외가 발생하면 완전히 누락됩니다. 예외를 사용하지 않는다고 주장하면 new메모리가 충분하지 않을 때 bad_alloc이 발생 하기 때문에 잘못된 것입니다.

또한이 시점에서 (bad_alloc이 throw 될 때) 스택이 풀려서 호출 스택을 올라가는 모든 함수의 모든 정리 코드가 누락되어 코드가 정리되지 않습니다.

스마트 포인터에 대한 조사를해야합니다. 위의 상황에서는 std::auto_ptr<>.

또한 C ++ 코드에서는 포인터가 NULL인지 (일반적으로 RAW 포인터가 없기 때문에) 확인할 필요 new가 없지만 NULL을 반환하지 않기 때문에 확인할 필요 가 없습니다.

또한 (C)와 달리 C ++에서는 코드에서 조기 반환을 보는 것이 일반적입니다. 이는 RAII 가 자동으로 정리를 수행하는 반면 C 코드에서는 함수 끝에 특수 정리 코드를 추가해야합니다 (코드와 비슷 함).


나는 다른 답변 (및 그들의 의견)이 모든 중요한 사항을 다루고 있다고 생각하지만 아직 제대로 수행되지 않은 한 가지가 있습니다.

대신 코드의 모습 :

bool foo() //lowercase bool is a built-in C++ type. Use it if you're writing C++.
{
  try {
    std::unique_ptr<int> p(new int);
    // lots of code, and just return true or false directly when you're done
  }
  catch (std::bad_alloc){ // new throws an exception on OOM, it doesn't return NULL
    cout<<" OOM \n";
    return false;
  }
}

음, 더 짧고 제가 볼 수있는 한 더 정확합니다 (OOM 케이스를 적절하게 처리). 가장 중요한 것은 정리 코드를 작성하거나 "내 반환 값이 초기화되었는지 확인하기 위해 특별한 작업을 수행 할 필요가 없었습니다. ".

이 코드를 작성했을 때만 알아 차린 한 가지 문제는 "이 시점에서 bRetVal의 가치는 무엇입니까?"입니다. 위에서 waaaaay로 선언되었고 언제 마지막으로 할당 되었습니까? 이보다 더 높은 지점에서. 반환 될 내용을 이해하기 위해 전체 함수를 읽어야합니다.

그리고 메모리가 해제된다는 것을 어떻게 확신합니까?

정리 레이블로 이동하는 것을 절대 잊지 않는다는 것을 어떻게 수 있습니까? 정리 레이블에서 거꾸로 작업 하여이를 가리키는 모든 고토를 찾아야하며 더 중요한 것은 거기에없는 것을 찾아야합니다. 함수가 제대로 정리되었는지 확인하기 위해 함수의 모든 경로를 추적해야합니다. 그것은 스파게티 코드처럼 읽습니다.

리소스를 정리해야 할 때마다 정리 코드를 복제 하는 것을 기억 해야 하므로 매우 취약한 코드입니다. 정리해야 할 유형으로 한 번 작성하지 않으시겠습니까? 그런 다음 필요할 때마다 자동으로 실행되는 것을 믿으시겠습니까?


다음에 대한 정책을 작성하기 전에 Linux 커널 메일 링 목록에서이 스레드 요약을 읽어야합니다 (Linus Torvalds의 응답에 특히주의) goto.

http://kerneltrap.org/node/553/2131


8 년 동안 프로그래밍을하면서 goto를 많이 사용했습니다. 그 대부분은 GW-BASIC 버전을 사용하던 첫해 였고 , 1980 년의 책에서 goto는 특정 경우에 사용됩니다. C ++에서 goto를 사용한 유일한 시간은 다음과 같은 코드가있을 때이며 더 나은 방법이 있는지 확실하지 않습니다.

for (int i=0; i<10; i++) {
    for (int j=0; j<10; j++)
    {
        if (somecondition==true)
        {
            goto finish;
        }
        //Some code
    }
    //Some code
}
finish:

goto가 여전히 많이 사용되는 곳을 아는 유일한 상황은 메인 프레임 어셈블리 언어이며, 제가 아는 프로그래머는 코드가 점프하는 위치와 이유를 문서화해야합니다.


일반적으로 프로그램을 설계하여 gotos의 필요성을 제한해야합니다. 반환 값의 "정리"를 위해 OO 기술을 사용하십시오. gotos를 사용하거나 코드를 복잡하게 만들 필요없이이를 수행하는 방법이 있습니다. gotos가 매우 유용한 경우 (예 : 깊게 중첩 된 범위)가 있지만 가능하면 피해야합니다.


Linux 커널에서 사용되는 것처럼 정리에 사용되는 goto는 단일 함수가 실행 취소해야 할 수있는 2 개 이상의 단계를 수행해야 할 때 잘 작동합니다. 단계는 메모리 할당 일 필요가 없습니다. 코드 또는 I / O 칩셋의 레지스터에 대한 구성 변경 일 수 있습니다. Goto는 소수의 경우에만 필요하지만 올바르게 사용하면 최상의 솔루션 이 될 수 있습니다 . 그들은 악하지 않습니다. 그들은 도구입니다.

대신에...

do_step1;
if (failed)
{
  undo_step1;
  return failure;
}

do_step2;
if (failed)
{
  undo_step2;
  undo_step1;
  return failure;
}

do_step3;
if (failed)
{
  undo_step3;
  undo_step2;
  undo_step1;
  return failure;
}

return success;

다음과 같은 goto 문으로 동일한 작업을 수행 할 수 있습니다.

do_step1;
if (failed) goto unwind_step1;

do_step2;
if (failed) goto unwind_step2;

do_step3;
if (failed) goto unwind_step3;

return success;

unwind_step3:
  undo_step3;

unwind_step2:
  undo_step2;

unwind_step1:
  undo_step1;

return failure;

이 두 가지 예가 주어지면 하나가 다른 것보다 낫다는 것이 분명합니다. RAII 군중에 관해서는 ... 해제가 항상 정확히 역순으로 발생한다는 것을 보장 할 수있는 한, 그 접근 방식에는 잘못된 것이 없습니다 : 3, 2, 1 그리고 마지막으로 일부 사람들은 코드에서 예외를 사용하지 않습니다. 컴파일러에게이를 비활성화하도록 지시합니다. 따라서 모든 코드가 예외로부터 안전하지 않아야합니다.


GOTO의 단점은 꽤 잘 논의되어 있습니다. 나는 1) 때때로 그것들을 사용해야하고 문제를 최소화하는 방법을 알아야하며, 2) 허용되는 일부 프로그래밍 기술은 변장으로 이동하므로주의해야합니다.

1) ASM이나 .bat 파일과 같이 GOTO를 사용해야 할 때 컴파일러처럼 생각하십시오. 코딩하려는 경우

 if (some_test){
  ... the body ...
}

컴파일러가하는 일을하십시오. 뒤에 오는 작업을 수행하지 않고 본문을 건너 뛰는 것이 목적인 레이블을 생성합니다.

 if (not some_test) GOTO label_at_end_of_body
  ... the body ...
label_at_end_of_body:

아니

 if (not some_test) GOTO the_label_named_for_whatever_gets_done_next
  ... the body ...

the_label_named_for_whatever_gets_done_next:

즉, 레이블의 목적은 무언가 하는 것이 아니라 무언가를 건너 뛰는 것입니다.

2) 내가 GOTO-in-disguise라고 부르는 것은 단지 몇 개의 매크로를 정의함으로써 GOTO + LABELS 코드로 변환 될 수있는 모든 것입니다. 한 예로 상태 변수와 while-switch 문을 사용하여 유한 상태 자동 장치를 구현하는 기술이 있습니다.

 while (not_done){
    switch(state){
        case S1:
            ... do stuff 1 ...
            state = S2;
            break;
        case S2:
            ... do stuff 2 ...
            state = S1;
            break;
        .........
    }
}

다음으로 바뀔 수 있습니다.

 while (not_done){
    switch(state){
        LABEL(S1):
            ... do stuff 1 ...
            GOTO(S2);
        LABEL(S2):
            ... do stuff 2 ...
            GOTO(S1);
        .........
    }
}

몇 개의 매크로를 정의하기 만하면됩니다. 거의 모든 FSA를 구조화 된 goto-less 코드로 바꿀 수 있습니다. GOTO-in-disguise 코드는 위장되지 않은 gotos와 동일한 스파게티 코드 문제에 빠질 수 있으므로 피하는 것을 선호합니다.

추가됨 : 안심할 수 있습니다. 좋은 프로그래머의 한 가지 특징은 공통 규칙이 적용되지 않는시기를 인식하는 것입니다.


Goto는 "테일 엔드 로직"이 모든 경우에 공통적으로 사용되는 경우 반복하지 않는 것이 좋습니다 (DRY). 특히 "switch"문 내에서 일부 switch-branch가 tail-end-commonality를 가질 때 종종 goto를 사용합니다.

switch(){
   case a:  ... goto L_abTail;
   case b: ... goto L_abTail;
L_abTail: <commmon stuff>
    break://end of case b
case c:
.....
}//switch

루틴의 중간에서 이러한 꼬리 끝 병합이 필요할 때 추가 중괄호를 도입하는 것이 컴파일러를 만족시키기에 충분하다는 것을 알고 계실 것입니다. 즉, 모든 것을 맨 위에 선언 할 필요가 없습니다. 그것은 실제로 열등한 가독성입니다.

...
   goto L_skipMiddle;
{
    int declInMiddleVar = 0;
    ....
}
L_skipMiddle: ;

이후 버전의 Visual Studio 에서 초기화되지 않은 변수의 사용을 감지하면 모든 분기에 할당 될 수 있다고 생각하더라도 항상 대부분의 변수를 초기화합니다. 할당되지 않은 변수를 참조하는 "추적"문을 쉽게 코딩 할 수 있습니다. 당신의 마음은 추적 문을 "실제 코드"로 생각하지 않지만 물론 Visual Studio는 여전히 오류를 감지합니다.

자신을 반복하지 않는 것 외에도 이러한 꼬리 끝 논리에 레이블 이름을 할당하면 멋진 레이블 이름을 선택하여 마음이 똑바로 유지되는 데 도움이되는 것 같습니다. 의미있는 레이블이 없으면 댓글에 동일한 내용이 표시 될 수 있습니다.

물론 실제로 리소스를 할당하는 경우 auto-ptr이 맞지 않으면 try-catch를 사용해야하지만 tail-end-merge-don't-repeat-yourself는 예외 안전이 별일 아니다.

요약하면, goto는 스파게티와 같은 구조를 코딩하는 데 사용할 수 있지만, 모든 경우는 아니지만 일부 경우에 공통되는 테일 엔드 시퀀스의 경우 goto는 코드의 가독성과 유지 관리 성을 향상시킵니다. 그렇지 않으면 복사 / 붙여 넣기를하여 나중에 누군가가 다른 사람이 아닌 다른 사람을 업데이트 할 수 있습니다. 따라서 교리에 열광하는 것이 비생산적 일 수있는 또 다른 경우입니다.


goto를 사용하여 정리 섹션으로 이동하면 많은 문제가 발생합니다.

첫째, 정리 섹션은 문제가 발생하기 쉽습니다. 그들은 낮은 응집력 (프로그램이 수행하려는 작업에 대해 설명 할 수있는 실제 역할이 없음), 높은 결합 (정확성은 코드의 다른 섹션에 매우 크게 의존 함)을 가지며 예외로부터 전혀 안전하지 않습니다. 정리를 위해 소멸자를 사용할 수 있는지 확인하십시오. 예를 들어가 int *p로 변경 되면 auto_ptr<int> pp가 가리키는 포인트가 자동으로 해제됩니다.

둘째, 지적했듯이 사용하기 오래 전에 변수를 선언해야하므로 코드를 이해하기가 더 어려워집니다.

셋째, 고토를 상당히 규율 적으로 사용하도록 제안하는 동안 더 느슨하게 사용하려는 유혹이 생기고 코드를 이해하기 어려워집니다.

goto가 적절한 상황은 거의 없습니다. 대부분의 경우 사용하고 싶은 유혹을 받으면 잘못하고 있다는 신호입니다.


이것은 고전적인 주제이므로 유해한 것으로 간주 되는 Dijkstra의 Go-to 진술 (원래 ACM에 게시 됨)으로 답장 할 것 입니다.


내 C ++ 코드에서 goto를 사용하는 유일한 두 가지 이유는 다음과 같습니다.

  • 레벨 2 이상의 중첩 루프 끊기
  • 다음과 같은 복잡한 흐름 (내 프로그램의 주석) :

    /* Analysis algorithm:
    
      1.  if classData [exporter] [classDef with name 'className'] exists, return it,
          else
      2.    if project/target_codename/temp/classmeta/className.xml exist, parse it and go back to 1 as it will succeed.
      3.    if that file don't exists, generate it via haxe -xml, and go back to 1 as it will succeed.
    
    */
    

여기서 코드 가독성을 위해이 주석 뒤에 step1 레이블을 정의하고 2 단계와 3 단계에서 사용했습니다. 실제로 60 개 이상의 소스 파일에서이 상황과 중첩 된 4 단계 중 하나만 goto를 사용한 곳입니다. 두 곳 밖에 없습니다.


C에서 모든 기능이 단일 출구 점 관용구의 전체 목적은 모든 정리 작업을 한곳에 두는 것이 었습니다. C ++ 소멸자를 사용하여 정리를 처리하는 경우 더 이상 필요하지 않습니다. 함수에있는 종료 지점 수에 관계없이 정리가 수행됩니다. 따라서 제대로 설계된 C ++ 코드에서는 더 이상 이런 종류의 작업이 필요하지 않습니다.


많은 사람들이 gotos에 대해 겁을 먹고 사악합니다. 그렇지 않습니다. 즉, 절대 필요하지 않습니다. 항상 더 나은 방법이 있습니다.

이런 유형의 작업을 수행하기 위해 goto가 "필요"하다는 것을 알게되면 거의 항상 내 코드가 너무 복잡하고 읽고 처리하기 쉬운 몇 가지 메서드 호출로 쉽게 나눌 수 있다는 것을 알게됩니다. 호출 코드는 다음과 같은 작업을 수행 할 수 있습니다.

// Setup
if(
     methodA() &&
     methodB() &&
     methodC()
 )
 // Cleanup

이것이 완벽하다는 것은 아니지만 문제가 무엇인지 명확하게 나타 내기 위해 모든 방법의 이름이 지정되므로 따르기가 훨씬 쉽습니다.

그러나 주석을 읽으면 팀이 goto 처리보다 더 긴급한 문제가 있음을 나타냅니다.


우리에게 제공하는 코드는 (거의) C ++ 파일 내에 작성된 C 코드입니다. 사용중인 메모리 정리의 종류는 C ++ 코드 / 라이브러리를 사용하지 않는 C 프로그램에서 괜찮습니다.

C ++에서 코드는 단순히 안전하지 않고 신뢰할 수 없습니다. C ++에서 요구하는 관리의 종류는 다르게 수행됩니다. 생성자 / 소멸자를 사용합니다. 스마트 포인터를 사용하십시오. 스택을 사용하십시오. 한마디로 RAII를 사용하십시오 .

코드는 다음과 같이 작성할 수 있습니다 (예 : C ++로 작성해야 함).

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<int> p = new int;

   // Lot of code...

   return bRetVal ;
}

(int를 새로 작성하는 것은 실제 코드에서 다소 어리석은 일이지만 int를 어떤 종류의 객체로도 바꿀 수 있으며 더 합리적입니다). T 유형의 객체가 있다고 가정 해 봅시다 (T는 정수, 일부 C ++ 클래스 등일 수 있음). 그러면 코드는 다음과 같습니다.

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<T> p = new T;

   // Lot of code...

   return bRetVal ;
}

또는 더 나은 방법은 스택을 사용하는 것입니다.

BOOL foo()
{
   BOOL bRetVal = FALSE;

   T p ;

   // Lot of code...

   return bRetVal;
}

어쨌든, 위의 예는 귀하의 예보다 읽기 쉽고 안전합니다.

RAII에는 다양한 측면 (즉, 스마트 포인터 사용, 스택 사용, 가변 길이 배열 대신 벡터 사용 등)이 있지만 모든 것은 가능한 한 적은 코드를 작성하여 컴파일러가 적절한 순간에 항목을 정리할 수 있도록하는 것입니다.


위의 모든 내용이 유효합니다. "많은 코드"로 표시된 섹션에있는 코드의 양을 줄임으로써 코드의 복잡성을 줄이고 goto의 필요성을 줄일 수 있는지 여부를 살펴볼 수도 있습니다. 귀하의 예에서. Additionaly delete 0는 유효한 C ++ 문입니다.


C ++에서 GOTO 레이블을 사용하는 것은 프로그래밍에 좋지 않은 방법입니다. OO 프로그래밍 (해체 자!)을 수행 하고 프로 시저 를 가능한 한 작게 유지 하려고 노력 하여 필요성을 줄일 수 있습니다.

귀하의 예제는 약간 이상해 보입니다 . NULL 포인터를 삭제할 필요없습니다 . 그리고 요즘에는 포인터를 할당 할 수 없을 때 예외가 발생합니다.

절차는 다음과 같이 작성할 수 있습니다.

bool foo()
{
    bool bRetVal = false;
    int p = 0;

    // Calls to various methods that do algorithms on the p integer
    // and give a return value back to this procedure.

    return bRetVal;
}

매우 드물게 사용자에게 메모리 부족 을 알리는 메모리 문제를 처리하는 주 프로그램에 try catch 블록을 배치해야 합니다. 이것은 OS 자체도 이에 대해 알려주지 않습니까?

또한 포인터 를 사용할 필요가 항상있는 것은 아니며 동적 작업 에만 유용 합니다 . (어디에서든 입력에 의존하지 않고 메서드 내에서 하나를 만드는 것은 실제로 동적이 아닙니다)


나는 그것이 goto항상 나쁘다고 말하지는 않을 것이지만, 당신이 그것을 사용하는 것은 가장 확실합니다. 이러한 종류의 "정리 섹션"은 1990 년대 초에 매우 일반적 이었지만 새 코드에이를 사용하는 것은 순수한 악입니다.


여기서하는 일을 피하는 가장 쉬운 방법은이 모든 정리를 일종의 간단한 구조에 넣고 인스턴스를 만드는 것입니다. 예를 들면 다음과 같습니다.

void MyClass::myFunction()
{
   A* a = new A;
   B* b = new B;
   C* c = new C;
   StartSomeBackgroundTask();
   MaybeBeginAnUndoBlockToo();

   if ( ... )
   {
     goto Exit;
   }

   if ( ... ) { .. }
   else
   {
      ... // what happens if this throws an exception??? too bad...
      goto Exit;
   }

Exit:
  delete a;
  delete b;
  delete c;
  StopMyBackgroundTask();
  EndMyUndoBlock();
}

다음과 같은 방식으로이 정리를 수행해야합니다.

struct MyFunctionResourceGuard
{
  MyFunctionResourceGuard( MyClass& owner ) 
  : m_owner( owner )
  , _a( new A )
  , _b( new B )
  , _c( new C )
  {
      m_owner.StartSomeBackgroundTask();
      m_owner.MaybeBeginAnUndoBlockToo();
  }

  ~MyFunctionResourceGuard()
  {
     m_owner.StopMyBackgroundTask();
     m_owner.EndMyUndoBlock();
  }

  std::auto_ptr<A> _a;
  std::auto_ptr<B> _b;
  std::auto_ptr<C> _c;

};

void MyClass::myFunction()
{
   MyFunctionResourceGuard guard( *this );

   if ( ... )
   {
     return;
   }

   if ( ... ) { .. }
   else
   {
      ...
   }
}

몇 년 전에 나는 goto를 피하고 C에서 예외 처리를하는 것과 모호하게 유사한 의사 관용구를 생각해 냈습니다. 아마 다른 누군가에 의해 이미 발명되었을 것이므로 "독립적으로 발견 한 것 같습니다.":)

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p=NULL;

   do
   {
       p = new int;
       if(p==NULL)
       {
          cout<<" OOM \n";
          break;
       }

       // Lot of code...

       bRetVal = TRUE;

    } while (false);

   if(p)
   {
     delete p;
     p= NULL;
   }

   return bRetVal;
}

종료 코드에 goto를 사용하는 것은 종료 기능이 있고 필요할 때 종료 기능 값을 반환하는 것과 같이 오버 헤드가 낮은 다른 솔루션이 많이 있기 때문에 나쁘다고 생각합니다. 일반적으로 멤버 함수에서는이 작업이 필요하지 않습니다. 그렇지 않으면 코드 부풀림이 너무 많이 발생했음을 나타낼 수 있습니다.

일반적으로 프로그래밍 할 때 "no goto"규칙에 대한 유일한 예외는 중첩 된 루프를 특정 수준으로 분리 할 때입니다. 이는 수학적 프로그래밍에서 작업 할 때만 수행해야하는 작업입니다.

예를 들면 :

for(int i_index = start_index; i_index >= 0; --i_index)
{
    for(int j_index = start_index; j_index >=0; --j_index)
        for(int k_index = start_index; k_index >= 0; --k_index)
            if(my_condition)
                goto BREAK_NESTED_LOOP_j_index;
BREAK_NESTED_LOOP_j_index:;
}

이 코드에는 많은 문제가 있으며 대부분은 이미 지적되었습니다. 예를 들면 다음과 같습니다.

  • 함수가 너무 깁니다. 일부 코드를 별도의 함수로 리팩토링하면 도움이 될 수 있습니다.

  • 일반 인스턴스가 정상적으로 작동 할 때 포인터를 사용합니다.

  • auto_ptr과 같은 STL 유형 을 활용하지 않음

  • 오류를 잘못 확인하고 예외를 포착하지 않습니다. (메모리가 부족하면 OS 자체를 작성하지 않는 한 소프트웨어가 해결할 수있는 것보다 더 큰 문제가 있기 때문에 대부분의 플랫폼에서 OOM을 확인하는 것은 무의미하다고 주장합니다)

저는 고토가 필요하지 않았고 고토를 사용하는 것이 더 큰 문제의 증상이라는 것을 항상 발견했습니다. 귀하의 사건도 예외는 아닙니다.


"GOTO"를 사용하면 프로그램의 "논리"와 입력 방식 또는 작동 방식이 변경됩니다.

GOTO 명령을 피하는 것은 항상 저에게 도움이되었으므로 필요할 것이라고 생각할 때 재 설계 만하면됩니다.

그러나 Assmebly 수준에서 이것을 살펴보면 "점프"를 사용하는 것은 GOTO를 사용하는 것과 같으며 항상 사용되지만 Assembly에서는 스택 및 기타 레지스터에 대해 알고있는 내용을 지울 수 있습니다. 전달합니다.

따라서 GOTO를 사용할 때 공동 코더가 진취적으로 생각하는 것처럼 소프트웨어가 "나타나"는지 확인하고 GOTO는 소프트웨어 imho에 "나쁜"영향을 미칩니다.

따라서 이것은 대체 솔루션이 아닌 GOTO를 사용하지 않는 이유에 대한 설명입니다. 이는 다른 모든 것이 구축되는 방식에 따라 매우 다르기 때문입니다.


나는 뭔가를 놓쳤을 수 있습니다 .P가 null이면 Exit라는 레이블로 점프 한 다음 null이 아닌지 테스트하여 삭제해야하는지 테스트합니다 (할당되지 않았기 때문에 필요하지 않음). 첫 번째 장소).

if / goto는 그렇지 않으며 p를 삭제할 필요가 없습니다. goto를 반환 false로 바꾸면 동일한 효과가 발생합니다 (이때 Exit 레이블을 제거 할 수 있습니다).

goto가 유용한 곳을 아는 유일한 곳은 불쾌한 파서 (또는 어휘 분석기)와 상태 머신 (대량의 CPP 매크로에 묻혀 있음)에 깊숙이 묻혀 있습니다. 이 두 가지 경우에는 매우 뒤틀린 논리를 더 간단하게 만드는 데 사용되었지만 매우 드뭅니다.

Functions (A calls A'), Try/Catches and setjmp/longjmps are all nicer ways of avoiding a difficult syntax problem.

Paul.


Ignoring the fact that new will never return NULL, take your code:

  BOOL foo()
  {
     BOOL bRetVal = FALSE;

     int *p=NULL;

     p = new int;

     if(p==NULL)
     {
        cout<<" OOM \n";
        goto Exit;
     }

     // Lot of code...

  Exit:
     if(p)
     {
        delete p;
        p= NULL;
     }

     return bRetVal;
  }

and write it like this:

  BOOL foo()
  {
     BOOL bRetVal = FALSE;

     int *p = new int;

     if (p!=NULL)
     {
        // Lot of code...

        delete p;
     }
     else
     {
        cout<<" OOM \n";
     }

     return bRetVal;
  }

참고URL : https://stackoverflow.com/questions/379172/use-goto-or-not

반응형