마지막으로 소멸자와의 개념적 차이점은 무엇입니까?


12

첫째, C ++에 '최종'구문이없는 이유는 무엇입니까? 그러나 다른 질문에 대한 오랜 의견의 토론은 별도의 질문을 보증하는 것으로 보입니다.

finallyC # 및 Java에서 기본적으로 범위 당 한 번만 (== 1) 존재할 수 있고 단일 범위에 여러 (== n) C ++ 소멸자가있을 수 있는 문제 외에도 본질적으로 동일한 것으로 생각합니다. (기술적 인 차이가 있습니다.)

그러나 다른 사용자 다음과 같이 주장했다 .

... 나는 dtor가 본질적으로 (릴리스 sematics)를위한 도구이고 마지막으로 (commit semantics)를위한 도구라고 말하려고했습니다. 이유를 모르는 경우 : finally 블록에서 서로 예외를 던지는 것이 합법적 인 이유와 소멸자에게 왜 그렇지 않은지를 고려하십시오. (어떤 의미에서, 그것은 데이터 대 제어입니다. 소멸자는 데이터를 방출하기위한 것이고, 마지막으로 제어를 방출하기위한 것입니다. 그것들은 다릅니다. C ++이 그것들을 함께 묶는 것은 불행합니다.)

누군가 이것을 정리할 수 있습니까?

답변:


6
  • 거래 ( try)
  • 오류 출력 / 응답 ( catch)
  • 외부 오류 ( throw)
  • 프로그래머 오류 ( assert)
  • 롤백 (가장 가까운 것은 기본적으로 지원하는 언어의 스코프 가드 일 수 있음)
  • 자원 해제 (소멸자)
  • 기타 거래 독립적 제어 흐름 ( finally)

finally기타 트랜잭션 독립적 제어 흐름보다 더 나은 설명을 얻을 수 없습니다 . 트랜잭션 및 오류 복구 사고 방식, 특히 소멸자와가 모두 포함 된 이론적 언어와 관련하여 상위 개념에 직접적으로 직접 매핑되는 것은 아닙니다 finally.

가장 본질적으로 나에게 부족한 것은 외부 부작용 롤백 개념을 직접 나타내는 언어 기능입니다. D와 같은 언어의 스코프 가드는 내가 생각할 수있는 가장 가까운 것입니다. 제어 흐름 관점에서 볼 때, 특정 기능 범위의 롤백은 예외 경로를 일반 경로와 구별해야하지만, 트랜잭션이 실패하더라도 트랜잭션이 실패하면 트랜잭션으로 인해 발생하는 부작용으로 인해 암시 적으로 롤백을 자동화해야합니다. . 예를 들어, succeeded소멸자의 롤백 논리를 방지하기 위해 try 블록의 끝에서 부울 값 을 true 와 같은 값으로 설정하면 소멸자와 함께 할 수 있습니다. 그러나이 작업을 수행하는 다소 우회적 인 방법입니다.

그것이 그렇게 많이 절약되지 않는 것처럼 보일 수 있지만, 부작용 반전은 가장 어려운 것 중 하나입니다 (예 : 예외 안전 일반 컨테이너를 작성하는 것이 너무 어렵습니다).


4

다른 방식으로 디자인되었지만 페라리와 대중 교통을 사용하여 상점에서 우유를 파는 데 사용할 수있는 것과 같은 방식입니다.

try / finally 구성을 모든 범위에 배치하고 finally 블록에서 모든 범위 정의 변수를 정리하여 C ++ 소멸자를 에뮬레이션 할 수 있습니다. 이것은 개념적으로 C ++의 기능입니다. 변수가 범위를 벗어날 때 (즉, 범위 블록의 끝에서) 컴파일러는 소멸자를 자동으로 호출합니다. 그러나 시도는 가장 먼저하고 마지막으로 각 범위에서 가장 마지막에 시도되도록 시도 / 최종적으로 배열해야합니다. 또한 각 객체에 표준을 정의하여 finally 블록에서 호출 할 상태를 정리하는 데 사용되는 특정 이름의 메소드를 갖도록해야합니다.하지만 언어가 제공하는 일반적인 메모리 관리를 그대로 둘 수있을 것 같습니다 지금 비운 물체를 원할 때 청소하십시오.

그렇기 때문에 .NET은 IDispose를 수동으로 관리되는 소멸자로 도입했으며 블록을 사용하여 수동 관리를 약간 쉽게하기 위해 시도했지만 여전히 실제로 수행하고 싶지 않은 것은 아닙니다. .


4

내 관점에서 볼 때 주된 차이점은 c ++ 의 소멸자 는 할당 된 리소스를 해제하기위한 암시 적 메커니즘 (자동으로 호출 됨)이며 try ... 마침내 이를 수행 하는 명시 적 메커니즘 으로 사용할 수 있다는 것입니다.

C ++ 프로그램에서 프로그래머는 할당 된 자원을 해제해야합니다. 이것은 일반적으로 클래스의 소멸자에서 구현되며 변수가 범위를 벗어나거나 삭제가 호출되면 즉시 수행됩니다.

C ++에서 new예외가있을 때 소멸자가 암시 적으로 해당 인스턴스의 리소스를 사용하지 않고 클래스의 로컬 변수를 만듭니다.

// c++
void test() {
    MyClass myClass(someParameter);
    // if there is an exception the destructor of MyClass is called automatically
    // this does not work with
    // MyClass* pMyClass = new MyClass(someParameter);

} // on test() exit the destructor of myClass is implicitly called

Java, c # 및 자동 메모리 관리 기능이있는 기타 시스템에서 시스템 가비지 콜렉터는 클래스 인스턴스가 삭제되는시기를 결정합니다.

// c#
void test() {
    MyClass myClass = new MyClass(someParameter);
    // if there is an exception myClass is NOT destroyed so there may be memory/resource leakes

    myClass.destroy(); // this is never called
}

암시 적 메커니즘은 없으므로 try를 사용하여 명시 적으로 프로그래밍해야합니다.

// c#
void test() {
    MyClass myClass = null;

    try {
        myClass = new MyClass(someParameter);
        ...
    } finally {
        // explicit memory management
        // even if there is an exception myClass resources are freed
        myClass.destroy();
    }

    myClass.destroy(); // this is never called
}

C ++에서 소멸자가 예외의 경우 힙 객체가 아닌 스택 객체로만 자동 호출되는 이유는 무엇입니까?
조르지오

@Giorgio 힙 리소스는 호출 스택에 직접 연결되지 않은 메모리 공간에 존재하기 때문입니다. 예를 들어, 2 개 스레드와 다중 스레드 응용 프로그램을 상상 A하고 B. 하나의 스레드가 throw되면 롤백 A's트랜잭션은에 할당 된 리소스를 파괴해서는 안됩니다 B. 예를 들어 스레드 상태는 서로 독립적이며 힙에 상주하는 영구 메모리는 둘 다에 독립적입니다. 그러나 일반적으로 C ++에서 힙 메모리는 여전히 스택의 객체에 연결됩니다.

@Giorgio 예를 들어, std::vector객체는 스택에있을 수 있지만 힙의 메모리를 가리킬 수 있습니다. 스택의 벡터 객체와 그 내용 (힙의 내용)은 모두 스택 해제 중에 할당 해제됩니다. 스택에서 벡터를 파괴하면 소멸자를 호출하여 힙에서 관련 메모리를 해제합니다 (그리고 힙 요소를 파괴합니다). 일반적으로 예외 안전을 위해 대부분의 C ++ 객체는 힙에서 메모리를 가리키는 핸들 만 있어도 스택 해제시 힙 및 스택 메모리를 모두 해제하는 프로세스를 자동화하더라도 스택에 존재합니다.

4

이 질문에 대한 답변을 보내 주셔서 감사합니다. :)

나는 소멸자와 그 finally개념이 다르다고 말하려고했습니다 .

  • 소멸자는 자원을 방출하기위한 것입니다 ( data )
  • finally발신자에게 반환하기위한 것입니다 ( control )

이 가상의 의사 코드를 생각해보십시오.

try {
    bar();
} finally {
    logfile.print("bar has exited...");
}

finally여기서는 자원 관리 문제가 아닌 제어 문제를 완전히 해결하고 있습니다.
소멸자에서 여러 가지 이유로 그렇게하는 것은 합리적이지 않습니다.

  • 어떤 것은 "인수 없습니다"또는 "창조"되고
  • 로그 파일에 인쇄하지 않으면됩니다 하지 자원 유출, 데이터 손상 등 (여기에 로그 파일이 다른 프로그램에 다시 공급되지 않는다는 것을 가정) 결과
  • logfile.print실패 는 합법적 이지만 파괴는 개념적으로 실패 할 수 없습니다

이번에는 Javascript와 같은 또 다른 예가 있습니다.

var mo_document = document, mo;
function observe(mutations) {
    mo.disconnect();  // stop observing changes to prevent re-entrance
    try {
        /* modify stuff */
    } finally {
        mo.observe(mo_document);  // continue observing (conceptually, this can fail)
    }
}
mo = new MutationObserver(observe);
return observe();

위의 예에서는 다시 해제 할 리소스가 없습니다.
사실, finally블록입니다 획득 목표, 잠재적으로 실패 할 수 있습니다 달성하기 위해 내부 자원을. 따라서 소멸자를 사용하는 것은 이치에 맞지 않습니다 (자바 스크립트가있는 경우).

반면에이 예에서는

b = get_data();
try {
    a.write(b);
} finally {
    free(b);
}

finally자원을 파괴하고 있습니다 b. 데이터 문제입니다. 문제는 컨트롤을 호출자에게 깨끗하게 되 돌리는 것이 아니라 리소스 누수를 피하는 것입니다.
실패는 선택 사항이 아니며 (개념적으로) 절대 발생하지 않아야합니다.
모든 릴리스 b는 반드시 인수와 쌍을 이루며 RAII를 사용하는 것이 좋습니다.

다시 말해, 둘 다 동일한 문제를 의미하지 않거나 두 문제 모두에 적합한 솔루션이라는 것을 시뮬레이션하는 데 사용할 수 있기 때문입니다.


감사. 나는 동의하지 않지만, 안녕 :-) 나는 다음 날에 철저한 반대 의견을 추가 할 수있을 것 같아요 ...
Martin Ba

2
finally(메모리가 아닌) 리소스를 해제하는 데 주로 사용되는 사실 이 어떻게 여기에 영향을 미칩니 까?
Bart van Ingen Schenau

1
@BartvanIngenSchenau : 현재 존재하는 언어가 내가 설명한 것과 일치하는 철학이나 구현을 가지고 있다고 결코 주장하지 않았습니다. 사람들은 아직 존재할 수있는 모든 것을 발명하지 못했습니다. 나는 두 가지 개념이 서로 다른 아이디어이고 사용 사례가 다르기 때문에 두 개념을 분리하는 데 가치가 있다고 주장했습니다. 호기심을 만족시키기 위해 D 는 둘 다 가지고 있다고 생각 합니다. 아마도 다른 언어들도있을 것입니다. 나는 그것이 관련성이 있다고 생각하지 않으며, 예를 들어 Java가 왜 유리한지를 신경 쓰지 않았습니다 finally.
user541686

1
JavaScript에서 마주 친 실제 예제는 긴 작업 (예외가 발생할 수 있음) 동안 마우스 포인터를 모래 시계로 일시적으로 변경 한 다음 finally절 에서 다시 정상으로 변경하는 함수입니다 . 는 C ++ 세계관은이 "자원"관리하는 클래스 소개 할 과제 의사 글로벌 변수를. 어떤 개념적 의미가 있습니까? 그러나 소멸자는 필요한 블록 끝 코드 실행을위한 C ++의 망치입니다.
dan04

1
@ dan04 : 정말 고마워, 이것에 대한 완벽한 예입니다. 나는 RAII가 이해가되지 않는 많은 상황을 겪을 것이라고 맹세 할 수 있었지만 나는 그것들을 생각하기가 너무 힘들었다.
user541686

1

k3b의 답변은 실제로 그것을 멋지게 표현합니다.

C ++의 소멸자는 할당 된 리소스를 해제하기위한 암시 적 메커니즘 (자동으로 호출 됨)이며 try ...는이를 명시 적 메커니즘 으로 사용할 수 있습니다 .

"자원"은 Jon Kalb : RAII는 책임 획득이 초기화됨을 의미합니다 .

어쨌든 암시 적 대 명시 적으로 이것은 실제로 다음과 같습니다.

  • d' tor는 개체의 수명이 끝날 때 (종종 범위의 끝과 일치하는 경우) 어떤 작업이 수행 될 것인지 암시 적으로 정의하는 도구입니다.
  • finally 블록은 범위 끝에서 수행 할 작업을 명시 적으로 정의하는 도구입니다.
  • 또한 기술적으로는 항상 최종적으로 던질 수 있지만 아래를 참조하십시오.

나는 그것이 개념적 부분이라고 생각합니다 ...


... 이제 IMHO에는 몇 가지 흥미로운 세부 사항이 있습니다.

또한 c'tor / d' tor는 소멸자에서 일부 코드를 실행하는 책임 외에 개념적으로 "획득"하거나 "생성"할 필요가 없다고 생각합니다. 마지막으로 수행하는 작업은 다음과 같습니다. 코드를 실행하십시오.

그리고 finally 블록의 코드는 확실히 예외를 던질 수 있지만, 명시 적으로나 암시 적으로 개념적으로 다르다는 것을 구별하기에는 충분하지 않습니다.

(또한, 나는 "좋은"코드가 마침내 던져 져야한다는 것을 전혀 확신하지 못한다. 아마도 그 자체에 대한 또 다른 질문 일 것이다.


Javascript 예제에 대해 어떻게 생각하십니까?
user541686

당신의 다른 주장에 대해 : "우리는 정말로 같은 것을 기록하고 싶습니까?" 예, 이것은 단지 예일 뿐이며 요점을 놓치고 있습니다. 예, 아무도 각 사례에 대해 더 구체적인 세부 정보를 기록하는 것을 금지하지 않았습니다. 여기서 중요한 점은 두 가지에 공통적 인 것을 기록하려는 상황 이 결코 없다고 주장 할 수 없다는 것 입니다. 일부 로그 항목은 일반적이며 일부는 특정 항목입니다. 둘 다 원해 그리고 다시, 로깅에 집중함으로써 요점을 완전히 놓치게됩니다. 10 줄 예제를 동기 부여하는 것은 어렵습니다. 요점을 놓치지 마십시오.
user541686

이러한 ... 해결하지
user541686

@Mehrdad-자바 스크립트 예제를 다루지 않았습니다. 내 생각에 대해 토론하는 데 다른 페이지가 필요하기 때문입니다. (나는 시도했지만, 그것을 건너 뛴 일관된 문구를 표현하는 데 너무 오래 걸렸다.)
Martin Ba

@Mehrdad-다른 점은 동의하지 않는 것 같습니다. 나는 당신이 차이점을 목표로하고있는 곳을 보았습니다. 그러나 그것들이 개념적으로 다르다는 것을 확신하지 못합니다. 주로 나는 주로 캠프에서 던지기가 정말로 나쁜 생각이라고 생각합니다 ( : 나도 당신의 생각 observer이 던지는 것은 정말 좋은 생각이 될 것이라고 예.)이 더 논의하려는 경우, 채팅을 열어 주시기 바랍니다. 당신의 주장에 대해 생각하는 것은 확실히 재미있었습니다. 건배.
Martin Ba
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.