'시도를 잡아라… 마지막으로'구성의 '마지막'부분이 필요한가?


25

C ++ 및 초기 버전의 PHP와 같은 일부 언어 finallytry ... catch ... finally구문 의 일부를 지원하지 않습니다 . 가 finally필요 이제까지? 코드가 항상 실행되기 때문에 없이 블록 뒤에 해당 코드를 배치하지 않는 이유는 무엇입니까? 왜 하나를 사용합니까? try ... catchfinally(나는 finally'catch'를 버릴 이유가 아닌 이유를 사용하거나 사용하지 않는 이유 / 동기를 찾고 있습니다.)


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

답변:


36

다른 사람들이 말한 것 외에도 catch 절 안에 예외가 발생할 수도 있습니다. 이걸 고려하세요:

try { 
    throw new SomeException();
} catch {
    DoSomethingWhichUnexpectedlyThrows();
}
Cleanup();

이 예제에서는 Cleanup()catch 절에서 예외가 발생하고 호출 스택에서 다음으로 높은 catch 가이를 잡기 때문에 함수가 실행되지 않습니다. finally 블록을 사용하면이 위험을 제거하고 코드를 더 깔끔하게 부팅 할 수 있습니다.


4
이론에 어긋나지 않고 '언어 X가 Y보다 낫다'는 간결하고 직접적인 답변을 주셔서 감사합니다.
Agi Hammerthief

56

다른 사람들이 언급했듯이 try가능한 모든 예외를 포착하지 않으면 명령문 뒤의 코드 가 실행 된다는 보장이 없습니다 . 즉,이 :

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   handleError();
} finally {
   cleanUp();
}

다음 과 같이 1 로 다시 쓸 수 있습니다 .

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   try {
       handleError();
   } catch (Throwable e2) {
       cleanUp();
       throw e2;
   }
} catch (Throwable e) {
   cleanUp();
   throw e;
}
cleanUp();

그러나 후자는 처리되지 않은 모든 예외를 포착하고 정리 코드를 복제하며 다시 던지는 것을 잊지 않아야합니다. 그래서 finally아니다 필요 하지만, 그건 유용합니다 .

Bjarne Stroustrup은 RAII가 더 낫 거나 최소한 대부분의 경우 충분 하다고 생각finally 하기 때문에 C ++은 없습니다 .

C ++에서 "최종"구문을 제공하지 않는 이유는 무엇입니까?

C ++은 거의 항상 더 나은 대안을 지원하기 때문에 "자원 획득은 초기화"기술 (TC ++ PL3 섹션 14.4)입니다. 기본 아이디어는 로컬 객체의 소멸자가 리소스를 해제 할 수 있도록 로컬 객체로 리소스를 나타내는 것입니다. 그렇게하면 프로그래머가 리소스를 해제하는 것을 잊을 수 없습니다.


1 모든 예외를 포착하고 스택 추적 정보를 잃지 않고 다시 던지는 특정 코드는 언어마다 다릅니다. 예외를 만들 때 스택 추적이 캡처되는 Java를 사용했습니다. C #에서는을 사용 throw;합니다.


8
또한 handleError()두 번째 경우 에는 예외를 잡아야합니다 .
Juri Robl

1
오류가 발생할 수도 있습니다. 나는 그것을 바꿔 것 catch (Throwable t) {}은 try와 .. 전체 초기 블록 주위에 catch 블록 (에서 캐치에 throw 가능 객체 handleError뿐만 아니라)
njzk2

1
실제로 호출 할 때 생략 한 여분의 try-catch를 추가하여 handleErro();finally 블록이 왜 유용한 지에 대한 더 나은 논쟁을 만들 것입니다 (원래 질문이 아니더라도).
Alex

1
이 답변은 실제로 C ++에 왜 finally뉘앙스가 없는지에 대한 질문을 다루지 않습니다 .
DeadMG

1
중첩 @AgiHammerthief try내부입니다 catch대한 특정의 예외. 둘째, 예외를 검사 할 때까지 오류를 성공적으로 처리 할 수 ​​있는지 또는 예외의 원인으로 인해 오류를 처리하지 못하게 할 수도 있습니다 (적어도 해당 수준에서). I / O를 수행 할 때 매우 일반적입니다. 재실행은 cleanUp실행 을 보장하는 유일한 방법 은 모든 것을 포착 하는 것이기 때문에 발생하지만 원래 코드는 catch (SpecificException e)블록 에서 발생하는 예외가 위쪽으로 전파 되도록 허용 합니다.
Doval

22

finally 블록은 일반적으로 여러 return 문을 사용할 때 가독성에 도움이되는 리소스를 정리하는 데 사용됩니다.

int DoSomething() {
    try {
        open_connection();
        return get_result();
    }
    catch {
        return 2;
    }
    finally {
        close_connection();
    }
}

vs

int DoSomething() {
    int result;
    try {
        open_connection();
        result = get_result();
    }
    catch {
        result = 2;
    }
    close_connection();
    return result;
}

2
이것이 가장 좋은 대답이라고 생각합니다. 일반적인 예외를 대체하기 위해 finally를 사용하는 것은 까다로워 보입니다. 올바른 유스 케이스는 자원 또는 유사한 조작을 정리하는 것입니다.
Kik

3
아마도 더 일반적인 것은 catch 블록이 아닌 try 블록 내부로 돌아 오는 것입니다.
Michael Anderson

내 마음에, 코드는의 사용법을 적절하게 설명하지 못합니다 finally. (내가 일하는 곳에서 여러 개의 반환 진술이 권장되지
않기

15

이미 추측 한 것처럼 C ++은 해당 메커니즘없이 동일한 기능을 제공합니다. 따라서 엄밀히 말하면 try/ finally메커니즘은 실제로 필요하지 않습니다.

즉, 그것없이 수행하면 나머지 언어가 디자인되는 방식에 대한 몇 가지 요구 사항이 부과됩니다. C ++에서는 동일한 액션 세트가 클래스의 소멸자에 구현됩니다. C ++에서 소멸자 호출이 결정적이므로 주로 (독점적으로?) 작동합니다. 이것은 결국 객체 수명에 대한 다소 복잡한 규칙으로 이어지며, 그 중 일부는 직관적이지 않습니다.

다른 언어의 대부분은 대신 가비지 수집 형식을 제공합니다. 가비지 수집에 대해서는 논란의 여지가 있지만 (예 : 다른 메모리 관리 방법에 대한 효율성) 일반적으로 한 가지가 아닙니다. 가비지 수집기가 개체를 "정리"하는 정확한 시간은 직접 연결되지 않습니다. 개체의 범위에. 이를 통해 단순히 올바른 작업을 위해 필요할 때 또는 정리 작업이 임의로 지연되지 않도록 소중한 리소스를 처리 할 때 정리를 결정해야하는 경우 사용을 방지합니다. try/ finally그러한 언어가 결정 론적 정리를 요구하는 상황을 처리 할 수있는 방법을 제공합니다.

이 기능에 대한 C ++ 구문이 Java보다 "친숙하지 않다"고 주장하는 사람들은 그 요점이 누락되었다고 생각합니다. 더구나, 구문을 뛰어 넘는 책임 분담에 대한 훨씬 더 중요한 점이 누락되어 있으며 코드 디자인 방식과 더 많은 관련이 있습니다.

C ++에서이 결정적 정리는 오브젝트의 소멸자에서 발생합니다. 즉, 객체 자체를 정리하도록 객체를 설계 할 수 있습니다 (일반적으로 설계해야 함). 이것은 객체 지향 디자인의 본질로갑니다. 클래스는 추상화를 제공하고 자체 불변성을 적용하도록 설계되어야합니다. C ++에서 정확하게 하나를 수행합니다. 그것이 제공하는 불변량 중 하나는 객체가 파괴되면 해당 객체 (메모리뿐만 아니라 모든 객체)에 의해 제어되는 리소스가 올바르게 파괴된다는 것입니다.

Java (및 유사)는 다소 다릅니다. finalize이론적으로 유사한 기능을 제공 할 수있는 기능을 지원하는 (다양한) 지원 은 지원이 약하기 때문에 기본적으로 사용할 수 없으며 실제로는 전혀 사용되지 않습니다.

결과적으로 클래스 자체 가 필요한 정리를 수행 할 수 있기 보다는 클래스 의 클라이언트 가 조치를 취해야합니다. 우리가 충분히 근시안적인 비교를한다면, 언뜻보기에이 차이가 아주 작고 Java가이 점에서 C ++과 상당히 경쟁적인 것 같습니다. 우리는 이런 식으로 끝납니다. C ++에서 클래스는 다음과 같습니다.

class Foo {
    // ...
public:
    void do_whatever() { if (xyz) throw something; }
    ~Foo() { /* handle cleanup */ }
};

... 그리고 클라이언트 코드는 다음과 같습니다.

void f() { 
    Foo f;
    f.do_whatever();
    // possibly more code that might throw here
}

Java에서는 클래스에서 객체가 조금 덜 사용되는 코드를 조금 더 교환합니다. 이것은 처음에는 상당히 균형 잡힌 것처럼 보입니다. 실제 코드와는 거리가 멀다. 가장 일반적인 코드에서는 클래스를 곳 에서만 정의 하지만 많은 곳에서 사용하기 때문이다. C ++ 접근 방식은 한 곳에서 정리를 처리하기 위해 해당 코드 만 작성한다는 것을 의미합니다. Java 접근 방식은 여러 위치에서, 해당 클래스의 객체를 사용하는 모든 장소에서 정리를 여러 번 처리하기 위해 해당 코드를 작성해야 함을 의미합니다.

간단히 말해서 Java 접근 방식은 기본적으로 우리가 제공하려는 많은 추상화가 "누설"임을 보장합니다. 결정 론적 정리가 필요한 모든 클래스는 클래스의 클라이언트가 정리할 대상 및 정리 방법에 대한 세부 사항을 알아야합니다. 클래스 자체에 숨겨져있는 세부 사항보다는

나는 "자바 방법"을 호출했습니다 있지만 위, try/ finally다른 이름으로 유사한 메커니즘은 완전히 자바로 제한되지 않습니다. 눈에 띄는 예를 들어, .NET 언어 (예 : C #)의 대부분 (모두?)은 동일합니다.

최근 Java와 C #의 반복은 이와 관련하여 "클래식"Java와 C ++ 사이의 중간 지점을 제공합니다. C #에서 정리를 자동화하려는 개체는 IDisposable인터페이스를 구현할 수 있습니다.이 인터페이스 Dispose는 C ++ 소멸자와 비슷한 (적어도 모호한) 메서드 를 제공합니다 . Java에서 / 와 유사하게 이것을 사용할 수 있지만 C # 은 범위가 입력 될 때 작성 될 자원을 정의하고 범위가 종료 될 때 소멸 되는 명령문으로 태스크를 조금 더 자동화합니다 . C ++에서 제공하는 자동화 및 확실성 수준은 여전히 ​​부족하지만 여전히 Java에 비해 크게 개선되었습니다. 특히, 클래스 디자이너는 방법 의 세부 사항을 중앙 집중화 할 수 있습니다tryfinallyusing의 구현에서 클래스를 처리합니다 IDisposable. 클라이언트 프로그래머에게 남은 using것은 IDisposable인터페이스가 필요할 때 사용 되도록하기 위해 명령문을 작성하는 부담이 적다 는 것입니다. Java 7 이상에서는 유죄를 보호하기 위해 이름이 변경되었지만 기본 개념은 기본적으로 동일합니다.


1
완벽한 답변. 소멸자는 C ++에서 가지고 있어야 기능입니다.
Thomas Eding

13

아무도 (웃기려는 의도 없음)이 발생하지 것으로 판단 할 수 없습니다 - 당신이하지 않는 필요 캐치 절을!

이것은 완벽하게 합리적입니다.

try 
{
   AcquireManyResources(); 
   DoSomethingThatMightFail(); 
}
finally 
{
   CleanUpThoseResources(); 
}

어떤 캐치 이 방법은 아무것도 할 수 없기 때문에 절 어디서나은 시력 없다 유용한 그 예외를; 호출 스택을 백업 할 수 있는 핸들러로 전파하도록 남겨둔다 . 모든 방법에서 예외를 잡아서 다시 던지는 것은 특히 같은 예외를 다시 던지는 경우 나쁜 생각입니다. 그것은 완전히 구조화 예외 처리가되는 방법에 위배 되어 (이고 작업에 단지 예외의 "모양"에 가까운 모든 방법에서 "오류 코드"를 반환하는).

그러나이 방법 해야 할 일은 "외부 세계"가 그 자체로 엉망인 것에 대해 아무것도 알 필요가 없도록 자체적으로 정리해야합니다. finally 절은 그렇게합니다. 호출 된 메소드의 동작 방식에 관계없이 finally 절은 메소드의 "출구시"에서 실행됩니다 (그리고 예외가 발생하는 시점 사이의 모든 finally 절에 대해서도 동일합니다. 이를 처리하는 최종 catch 절); 각각은 호출 스택 "풀기"로 실행됩니다.


9

예상치 못한 예외와 예외가 발생하면 어떻게됩니까? try는 중간에 종료되며 catch 절이 실행되지 않습니다.

finally 블록은이를 지원하고 예외가 발생하더라도 정리가 수행되도록 보장하는 것입니다.


4
finally"예기치 않은"예외 catch(Object)또는 catch(...)catch-alls를 방지 할 수 있기 때문에의 충분한 이유가 아닙니다 .
MSalters

1
해결 방법처럼 들립니다. 개념적으로 마침내 더 깨끗합니다. 거의 사용하지 않는다고 고백해야하지만.
quick_now

7

일부 언어는 객체에 대해 생성자와 소멸자를 모두 제공합니다 (예 : C ++). 이러한 언어를 사용하면 finally소멸자에서 일반적으로 수행되는 대부분의 작업을 수행 할 수 있습니다 . 따라서 해당 언어에서 finally조항은 불필요한 것일 수 있습니다.

소멸자가없는 언어 (예 : Java)에서는 finally절 없이 올바른 정리를 수행하는 것이 어렵습니다 (아마도 불가능할 수도 있음) . NB-Java에는 finalise메소드가 있지만 호출 될 것이라는 보장은 없습니다.


소멸자가 결정론 적일 때 자원을 청소하는 데 도움이된다는 점에 유의하는 것이 좋습니다. 객체가 언제 파괴 및 / 또는 가비지 수집 될지 모른다면 소멸자는 충분히 안전하지 않습니다.
Morwenn

@ Morwenn-좋은 지적. Java에 대한 언급으로 힌트를 주 finalise었지만 현재로서는 소멸자 / 결국에 관한 정치적 주장에 들어가기를 원하지 않습니다.
OldCurmudgeon

C ++에서 파괴는 결정적입니다. 자동 객체를 포함하는 스코프가 종료되면 (예 : 스택에서 튀어 나옴) 소멸자가 호출됩니다. (C ++을 사용하면 힙뿐만 아니라 스택에 객체를 할당 할 수 있습니다.)
Rob K

@RobK-그리고 이것은 다른 언어 finalisefinalise메커니즘 과 매우 표현적이고 비교할 수있는 확장 가능한 맛과 oop-like 메커니즘을 가진 정확한 기능입니다 .
OldCurmudgeon

1

마지막으로 시도하고 catch를 시도하는 것은 "try"키워드 만 공유하는 두 가지입니다. 개인적으로 나는 그것이 다른 것을보고 싶었다. 그것들을 함께 보는 이유는 예외가 "점프"를 생성하기 때문입니다.

그리고 마지막으로 프로그래밍 흐름이 급증하더라도 코드를 실행하도록 설계되었습니다. 예외 때문인지 다른 이유 때문인지. 리소스를 확보하고 점프에 대해 걱정할 필요없이 정리할 수있는 깔끔한 방법입니다.


3
.NET에서는 별도의 메커니즘을 사용하여 구현됩니다. 그러나 Java에서는 JVM이 인식하는 유일한 구성은 의미 상 직접적으로 지원 try catch하지만 그렇지 않은 패턴 인 "on go goto"와 동일합니다 try finally. 후자를 사용하는 코드는 finally실행해야 할 코드의 모든 지점 에서 블록 의 내용을 복사하여 전자 만 사용하여 코드로 변환됩니다 .
supercat

@supercat, Java에 대한 추가 정보를 주셔서 감사합니다.
Pieter B

1

이 질문은 C ++을 언어로 지정하지 않기 때문에 C ++과 Java의 혼합을 고려할 것입니다. 왜냐하면 그들은 객체 파괴에 대한 다른 접근 방식을 취하기 때문에 대안 중 하나로 제안되고 있습니다.

try-catch 블록 다음에 코드가 아닌 finally 블록을 사용하는 이유

  • try 블록에서 일찍 돌아옵니다.

    Database db = null;
    try {
     db = open_database();
     if(db.isSomething()) {
       return 7;
     }
     return db.someThingElse();
    } finally {
      if(db!=null)
        db.close();
    }
    

    와 비교 :

    Database db = null;
    int returnValue = 0;
    try {
     db = open_database();
     if(db.isSomething()) {
       returnValue = 7;
     } else {
       returnValue = db.someThingElse();
     }
    } catch(Exception e) {
      if(db!=null)
        db.close();
    }
    return returnValue;
    
  • 캐치 블록에서 일찍 돌아옵니다. : 비교

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      return 7;
    } catch (DBIsADonkeyException e ) {
      return 11;
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs :

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null) 
        db.close();
      return 7;
    } catch (DBIsADonkeyException e ) {
      if(db!=null)
        db.close();
      return 11;
    }           
    db.close();
    
  • 예외를 다시 발생시킵니다. 비교:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      throw convertToRuntimeException(e,"DB was wonkey");
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs :

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null)
        db.close();
      throw convertToRuntimeException(e,"DB was wonkey");
    } 
    if(db!=null)
      db.close();
    

이 예제는 그렇게 나쁘게 보이지는 않지만 종종 이러한 사례 중 몇 가지가 상호 작용하고 하나 이상의 예외 / 자원 유형이 작동합니다. finally코드가 복잡하게 유지되는 악몽이되는 것을 방지 할 수 있습니다.

이제 C ++에서 이것들은 스코프 기반 객체로 처리 될 수 있습니다. 그러나 IMO는이 접근법 1에 대해 두 가지 단점이있다. 2. 파괴의 순서와 반대되는 건설 순서는 상황을 덜 명확하게 만들 수 있습니다.

Java에서는 종료 시점을 알 수 없기 때문에 정리 작업을 수행하기 위해 finalize 메소드를 연결할 수 없습니다. 일이-당신이 예상했던 것보다 더 빠르거나 늦을 때-종종 핫스팟 컴파일러가 시작될 때 변화 할 수 있습니다 ... 한숨 ...)


1

프로그래밍 언어에서 논리적으로 "필수"인 것은 모두 지침입니다.

assignment a = b
subtract a from b
goto label
test a = 0
if true goto label

모든 알고리즘은 위의 지침 만 사용하여 구현할 수 있으며, 다른 모든 언어 구성은 프로그램을보다 쉽게 ​​작성하고 다른 프로그래머가 이해할 수 있도록하기 위해 존재합니다.

이러한 최소 명령어 세트를 사용하는 실제 하드웨어는 oldie worldy computer 를 참조하십시오 .


1
당신의 대답은 확실히 사실이지만, 나는 어셈블리 코드를 작성하지 않습니다. 너무 아파요. 기능을 사용하는 이유를 묻습니다. 언어의 최소 명령어 집합이 아닌 기능을 지원하는 언어로 요점을 알 수 없습니다.
Agi Hammerthief

1
요점은이 5 가지 연산을 구현하는 모든 언어가 비록 아무리 대단하지만 알고리즘을 구현할 수 있다는 것입니다. 목표가 단순히 알고리즘을 구현하는 것이라면 고급 언어로 된 대부분의 언어 / 구동자는 "필요"하지 않습니다. 읽을 수있는 유지 보수 가능한 코드를 신속하게 개발하는 것이 목표라면 대부분이 필요하지만 "읽기"및 "유지 가능"은 측정 가능하고 매우 주관적이지 않습니다. 멋진 언어 개발자는 많은 기능을 제공합니다. 일부 언어를 사용하지 않으면 사용하지 마십시오.
James Anderson

0

실제로 저에게 더 큰 차이는 일반적으로 finally소멸자 를 지원 하지만 소멸자가없는 언어에 있습니다. 소거를 수동으로 처리하지 않고 소멸자를 통해 소멸자를 통해 "정화"와 관련된 모든 논리 (두 가지 범주로 구분)를 모델링 할 수 있기 때문입니다. 모든 관련 기능의 논리. C # 또는 Java 코드가 수동으로 뮤텍스 잠금 해제 및 파일을 finally블록으로 닫는 것과 같은 일을하는 것을 볼 때 소멸자를 통해 C ++에서 모든 책임이 인간을 자유롭게하는 방식으로 자동화되면 C 코드와 비슷합니다.

그러나 C ++이 포함되어 finally있고 두 가지 유형의 정리가 있기 때문에 여전히 편리한 편입니다 .

  1. 로컬 리소스 파괴 / 해제 / 잠금 해제 / 닫기 등
  2. 외부 부작용 실행 취소 / 롤백 (소멸자가 이에 적합 함).

두 번째는 최소한 자원 파괴 아이디어에 직관적으로 매핑되지는 않지만 변경 사항이 커밋되기 전에 파괴 될 때 자동으로 롤백하는 스코프 가드로 잘 수행 할 수 있습니다. 이 finally틀림없이 적어도 제공 약간 (단지 조그마한 비트에 의해) 범위 가드보다 일에 더 간단한 메커니즘을.

그러나 훨씬 더 간단한 메커니즘은 rollback이전에는 어떤 언어에서도 본 적이없는 블록입니다. 예외 처리와 관련된 언어를 설계 한 적이 있다면 그것은 일종의 파이프 꿈입니다. 다음과 유사합니다.

try
{
    // Cause external side effects. These side effects should
    // be undone if we don't finish successfully.
}
rollback
{
    // Reverse external side effects. This block is *only* executed 
    // if the 'try' block above faced a premature return out 
    // of the function. It is different from 'finally' which 
    // gets executed regardless of whether or not the function 
    // exited prematurely. This block *only* gets executed if we 
    // exited prematurely from  the try block so that we can undo 
    // whatever side effects it failed to finish making. If the try 
    // block succeeded and didn't face a premature exit, then we 
    // don't want this block to execute.
}

이는 부작용 롤백을 모델링하는 가장 간단한 방법이며 소멸자는 로컬 리소스 정리를위한 완벽한 메커니즘입니다. 이제 스코프 가드 솔루션에서 몇 줄의 코드를 추가로 저장하지만이 언어를 사용하려는 이유는 부작용 롤백이 예외 처리에서 가장 무시되는 (가장 까다로운) 측면이기 때문입니다. 가변성을 중심으로하는 언어로 이 기능은 개발자가 함수가 부작용을 일으켜 완료되지 않을 때마다 트랜잭션을 롤백하는 관점에서 예외 처리에 대한 올바른 방법을 생각하고 사람들이 롤백을 제대로 수행하는 것이 얼마나 어려운지를 알면 우선 부작용없이 더 많은 기능을 작성하는 것이 좋습니다.

타임 스탬프 기록과 같이 종료 방법에 관계없이 함수 종료시 무엇이든 상관없이 기타 작업을 수행하려는 모호한 경우도 있습니다. 이 finally단지 람다와 매우 편리하게 당신이 잘 그것을 할 수 있지만 (정말 이상한 느낌 만 타임 스탬프를 기록하기위한 목적으로 소멸자를 사용하여 객체를 인스턴스화하려고하기 때문에 작업에 대한 가장 간단하고 완벽한 솔루션은 틀림없이 ).


-9

C ++ 언어에 대한 다른 많은 특이한 사항들과 마찬가지로 try/finally, 실제 설계 작업 을 전혀 수행 하지 않은 것으로 보이는 언어로 호출 할 수 있는 구조 의 부족은 설계 결함 입니다.

RAII (정리를 위해 스택 기반 개체에서 범위 기반 결정적 소멸자 호출 사용)에는 두 가지 심각한 결함이 있습니다. 첫 번째는 Liskov 대체 원칙을 위반하는 가증 ​​한 스택 기반 개체를 사용해야 한다는 것입니다. C ++ 이전이나 이후에 다른 OO 언어가 엡실론 내에서 사용하지 않은 이유는 충분합니다. D는 C ++에 크게 기반을 둔 것으로 간주되며 어쨌든 시장 점유율이 없으며 문제의 원인을 설명하는 것은이 답변의 범위를 벗어납니다.

둘째, 할 finally수있는 것은 객체 파괴의 상위 집합입니다. C ++에서 RAII로 수행되는 대부분의 작업은 다음 언어로 가비지 콜렉션이없는 Delphi 언어로 설명됩니다.

myObject := MyClass.Create(arguments);
try
   doSomething(myObject);
finally
   myObject.Free();
end;

이것은 명시적인 RAII 패턴입니다. 위의 첫 번째와 세 번째 줄과 동등한 내용 만 포함하는 C ++ 루틴을 작성하는 경우 컴파일러가 생성하는 결과는 기본 구조에서 작성한 것과 유사하게 나타납니다. 그리고 try/finallyC ++이 제공 하는 구문에 액세스 할 수 있기 때문에 C ++ 개발자는 다소 근시안적 인 견해 try/finally를 갖게됩니다.

그러나 숙련 된 개발자가 finally구성으로 할 수있는 다른 작업이 있습니다 . 예외가 제기 되더라도 결정 론적 파괴에 관한 것이 아닙니다. 예외가 발생하더라도 결정 론적 코드 실행 에 관한 것 입니다.

Delphi 코드에서 일반적으로 볼 수있는 또 다른 사항은 다음과 같습니다. 사용자 컨트롤이 바인딩 된 데이터 세트 객체. 데이터 세트는 외부 소스의 데이터를 보유하며 컨트롤은 데이터의 상태를 반영합니다. 많은 데이터를 데이터 세트에로드하려는 경우 데이터 바인딩이 일시적으로 비활성화되어 UI에 이상한 일이 발생하지 않도록 입력 한 모든 새로운 레코드로 반복해서 업데이트하려고합니다. 따라서 다음과 같이 코딩합니다.

dataset.DisableControls();
try
   LoadData(dataset);
finally
   dataset.EnableControls();
end;

분명히, 여기에 파괴되는 물체가 없으며, 필요하지 않습니다. 코드는 간단하고 간결하며 명시 적이며 효율적입니다.

이것이 C ++에서 어떻게 이루어 집니까? 글쎄, 먼저 전체 클래스코딩해야합니다 . 아마도 전화 DatasetEnabler또는 그런 것입니다. 그것의 전체 존재는 RAII 조력자 일 것입니다. 그런 다음 다음과 같이해야합니다.

dataset.DisableControls();
{
   raiiGuard = DatasetEnabler(dataset);
   LoadData(dataset);
}

그렇습니다. 겉보기에 불필요한 중괄호는 적절한 범위를 관리하고 데이터 집합이 메서드의 끝이 아닌 즉시 다시 활성화되도록하는 데 필요합니다. 따라서 이집트 괄호를 사용하지 않는 한 코드 줄이 적지 않습니다. 오버 헤드가있는 불필요한 오브젝트를 작성해야합니다. (C ++ 코드는 빠르지 않아야합니까?) 명시 적이지는 않지만 컴파일러 마법에 의존합니다. 실행되는 코드는이 메소드의 어느 곳에서도 설명되지 않지만, 완전히 다른 클래스, 아마도 완전히 다른 파일에 상주 합니다 . 즉, try/finally블록을 직접 작성할 수있는 것보다 더 나은 솔루션은 아닙니다 .

이런 종류의 문제는 언어 설계에서 그 이름이있는 추상화 공통이라고 할 수 있습니다 . 상위 수준의 구문이 하위 수준의 구문 위에 구축 된 후 하위 수준의 구문이 언어로 직접 지원되지 않는 경우,이를 사용하려는 사람은 언어의 관점에서이를 다시 구현해야합니다. 높은 수준의 구조, 종종 코드 가독성과 효율성 모두에 가파른 처벌을받습니다.


의견은 질문과 답변을 명확하게하거나 개선하기위한 것입니다. 이 답변에 대해 토론하고 싶다면 대화방으로 이동하십시오. 고맙습니다.
maple_shaft
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.