가비지 수집 언어의 객체 소멸자 패러다임이 널리 보급되지 않은 이유는 무엇입니까?


27

가비지 수집 언어 디자인 관련 의사 결정에 대한 통찰력을 찾고 있습니다. 어쩌면 언어 전문가가 나를 밝힐 수 있습니까? 나는 C ++ 배경에서 왔 으므로이 지역은 당황 스럽습니다.

Ruby, Javascript / ES6 / ES7, Actionscript, Lua 등과 같은 OOPy 객체 지원을 통해 거의 모든 현대 가비지 수집 언어가 소멸자 / 최종 패러다임을 완전히 생략합니다. 파이썬은 그 class __del__()방법을 가진 유일한 것 같습니다 . 왜 이런거야? 객체에 대한 소멸자 / 완료 방법의 효과적인 구현을 방해하는 자동 가비지 수집 기능이있는 언어의 기능적 / 이론적 제한이 있습니까?

이러한 언어가 메모리를 관리 할 가치가 있는 유일한 리소스 로 간주하는 것은 매우 부족합니다 . 소켓, 파일 핸들, 응용 프로그램 상태는 어떻습니까? 메모리가 아닌 리소스를 정리하기위한 커스텀 로직을 구현할 수있는 능력과 객체 마무리 상태를 밝히지 myObject.destroy()않으면, 커스텀 스타일 호출로 애플리케이션을 어지럽히고, 정리 로직을 "클래스"외부에 배치하고, 캡슐화 시도를 중단하고 gc가 자동으로 처리하지 않고 인적 오류로 인한 리소스 누수에 대한 애플리케이션.

이러한 언어가 객체 처리에 대한 사용자 지정 논리를 실행할 방법이없는 언어 설계 결정은 무엇입니까? 나는 타당한 이유가 있다고 상상해야한다. 이 언어로 인해 객체 파괴 / 완료를 지원하지 않는 기술적 및 이론적 결정을 더 잘 이해하고 싶습니다.

최신 정보:

아마도 내 질문을 표현하는 더 좋은 방법 일 것입니다.

왜 언어가 클래스 또는 클래스와 유사한 구조를 가진 객체 인스턴스에 대한 개념을 사용자 정의 인스턴스화 (생성자)와 함께 가지고 있지만 파괴 / 완료 기능을 완전히 생략하는 이유는 무엇입니까? 자동 가비지 콜렉션을 제공하는 언어는 오브젝트가 더 이상 사용되지 않을 때 100 % 확실하게 알기 때문에 오브젝트 파괴 / 완료를 지원하는 주요 후보로 보입니다. 그러나 대부분의 언어는이를 지원하지 않습니다.

필자는 파괴자가 호출되지 않을 수도 있다고 생각하지 않습니다 .gcs는 피하도록 설계된 핵심 메모리 누수 일 것입니다. 소멸자 / 완료자가 미래에 결정되지 않은 시간이 될 때까지 호출되지 않을 수도 있지만 Java 또는 Python이 기능을 지원하는 것을 막지 않았다는 가능한 주장을 알 수 있습니다.

어떤 형태의 객체 마무리를 지원하지 않는 핵심 언어 설계 이유는 무엇입니까?


9
어쩌면 finalize/ destroy거짓말인가? 그것이 실행될 것이라는 보장은 없습니다. 그리고, (자동 가비지 콜렉션이 제공된시기)를 알지 못하고 필요한 컨텍스트가 여전히 존재하는 경우 (이미 수집 된 상태 일 수 있음). 따라서 다른 방법으로 일관성있는 상태를 유지하는 것이 더 안전하며 프로그래머가 강제로 수행하도록 할 수 있습니다.
Raphael

1
나는이 질문이 경계선을 벗어난 주제라고 생각한다. 우리가 원하는 종류의 프로그래밍 언어 디자인 문제입니까, 아니면 프로그래밍을 지향하는 사이트의 문제입니까? 커뮤니티 투표 부탁드립니다.
Raphael

14
PL 디자인에서 좋은 질문입니다.
Andrej Bauer

3
이것은 실제로 정적 / 동적 구분이 아닙니다. 많은 정적 언어에는 종료자가 없습니다. 실제로, 소수의 최종 언어를 가진 언어가 아닌가?
Andrej Bauer

1
여기에 몇 가지 질문이 있다고 생각하십시오. 용어를 조금 더 정의하면 더 좋습니다. java에는 객체 파괴와 관련이 없지만 메소드 종료와 연결된 finally 블록이 있습니다. 자원을 다루는 다른 방법들도 있습니다. 예를 들어, 자바에서, 연결 풀은 사용되지 않은 [x] 많은 시간의 연결을 처리하고이를 되 찾을 수 있습니다. 우아하지는 않지만 작동합니다. 귀하의 질문에 대한 답변의 일부는 가비지 수집이 대략 비 결정적이며 즉각적인 프로세스가 아니며 더 이상 사용되지 않는 객체가 아니라 메모리 제약 / 천장이 트리거되는 것입니다.
vzn

답변:


10

개체가 리소스를 정리하는 방법을 알고있는 패턴은 세 가지 관련 범주로 구분됩니다. 소멸자finalizer 와 혼동하지 말고 가비지 수집과 관련된 것은 하나뿐입니다.

  • 종료 자 패턴 : 프로그래머가 정의한 자동으로 선언 된 정리 메소드.

    종료자는 가비지 수집기에서 할당을 해제하기 전에 자동으로 호출됩니다. 사용 된 가비지 수집 알고리즘이 객체 수명주기를 결정할 수있는 경우이 용어가 적용됩니다.

  • 소멸자 패턴 : 정리 방법은 자동으로 만라고도, 프로그래머에 의해 정의 된 자동으로 선언했다.

    소멸자는 스택 할당 된 개체에 대해 자동으로 호출 될 수 있지만 (객체 수명이 결정적이므로) 힙 할당 된 개체에 대해 가능한 모든 실행 경로에서 명시 적으로 호출되어야합니다 (객체 수명이 결정적이지 않기 때문에).

  • 디스 포저 패턴 : 정리 방법은 정의와 프로그래머라고 선언했다.

    프로그래머는 폐기 방법을 만들어 스스로 호출합니다 myObject.destroy(). 폐기가 절대적으로 필요한 경우 가능한 모든 실행 경로에서 폐기자를 호출해야합니다.

파이널 라이저는 당신이 찾고있는 드로이드입니다.

종료 자 패턴 (질문에서 요구하는 패턴)은 가비지 수집기에 의한 상호 교정을 위해 객체를 시스템 리소스 (소켓, 파일 설명자 등)와 연결하는 메커니즘입니다. 그러나 종료자는 기본적으로 사용중인 가비지 수집 알고리즘에 의해 결정됩니다.

이 가정을 고려하십시오.

자동 가비지 콜렉션을 제공하는 언어는 객체를 더 이상 사용하지 않을 때 100 % 확실하게 알고 있습니다.

기술적으로 거짓입니다 (@babou 감사합니다). 가비지 콜렉션은 기본적으로 객체가 아니라 메모리에 관한 것입니다. 수집 알고리즘이 객체의 메모리가 더 이상 사용되지 않는다는 것을 알고 있는지의 여부는 알고리즘과 객체가 서로를 어떻게 참조하는지에 달려 있습니다. 두 가지 유형의 런타임 가비지 수집기에 대해 이야기합시다. 이를 기본 기술로 변경하고 보강하는 방법에는 여러 가지가 있습니다.

  1. GC 추적 이러한 추적 메모리는 객체가 아닙니다. 그렇게하지 않으면 메모리에서 객체에 대한 참조를 다시 유지하지 않습니다. 기능을 보강하지 않으면 메모리에 도달 할 수없는 시점을 알고 있더라도 이러한 GC는 언제 객체를 완성 할 수 있는지 알 수 없습니다. 따라서 파이널 라이저 호출은 보장되지 않습니다.

  2. 기준 계수 GC . 이들은 객체를 사용하여 메모리를 추적합니다. 지시 된 참조 그래프를 사용하여 객체 도달 가능성을 모델링합니다. 객체 참조 그래프에 사이클이있는 경우 사이클의 모든 객체에는 프로그램 종료 전까지는 종료자가 호출되지 않습니다. 다시 한 번 파이널 라이저 호출은 보장되지 않습니다.

TLDR

가비지 콜렉션은 어렵고 다양합니다. 프로그램 종료 전에 종료 자 호출을 보장 할 수 없습니다.


이것이 정적 대 동적이 아닌 것이 맞습니다. 가비지 수집 언어의 문제입니다. 가비지 콜렉션은 복잡한 문제이며 고려해야 할 많은 경우가있는 주된 이유 일 것입니다 (예 : 로직 처리로 finalize()인해 정리중인 오브젝트를 다시 참조하게 되면 어떻게됩니까 ?). 그러나 프로그램 종료 전에 종료자가 호출되는 것을 보장 할 수 없다고해서 Java가이를 지원하는 것을 막지는 못했습니다. 대답이 틀렸다고 말하지 않고 아마도 불완전 할 수도 있습니다. 여전히 좋은 소식입니다. 고맙습니다.
dbcb

피드백 감사드립니다. 내 대답을 완성하려는 시도는 다음과 같습니다. 종료자를 명시 적으로 생략하면 언어는 사용자가 자신의 리소스를 관리하도록합니다. 많은 유형의 문제의 경우 아마도 단점 일 것입니다. 나는 파이 나라의 힘을 가지고 있기 때문에 개인적으로는, 자바의 선택을 선호 하고 작성하고 내 자신의 디스 포저를 사용하는 저를 중지 아무것도 없다. 자바의 말이다. "이봐, 프로그래머. 넌 바보가 아니니까 여기 파이널 라이저가있다. 조심해라."
kdbanman

1
가비지 수집 언어를 다루는 것을 반영하여 원래 질문을 업데이트했습니다. 당신의 대답을 받아들입니다. 답변 해 주셔서 감사합니다.
dbcb

도와 줄 수있어서 기뻐. 내 의견 설명이 답을 더 명확하게 했습니까?
kdbanman

2
좋아요 저에게있어 실제 답은 언어가 구현하지 않기로 선택한다는 것입니다.인지 된 가치는 기능을 구현하는 문제보다 중요하지 않기 때문입니다. (Java와 Python이 보여주는 것처럼) 불가능하지는 않지만 많은 언어가 선택하지 않기로 결정한 상충 관계가 있습니다.
dbcb

5

간단히 말해서

마무리 작업은 가비지 수집기에서 처리하는 간단한 문제가 아닙니다. 참조 카운팅 GC와 함께 사용하기는 쉽지만이 GC 제품군은 종종 불완전하므로 일부 객체와 구조의 파괴 및 종료를 명시 적으로 트리거하여 메모리 누수를 보완해야합니다. 가비지 수집기를 추적하는 것이 훨씬 효과적이지만 사용되지 않은 메모리를 식별하는 것보다 시간과 공간 및 복잡성이 큰 복잡한 관리를 요구하는 것과 달리 마무리 및 파괴 할 객체를 식별하기가 훨씬 어렵습니다. 구현.

소개

가비지 수집 언어가 설명에 표시된 것처럼 가비지 수집 프로세스 내에서 파괴 / 완료를 자동으로 처리하지 않는 이유는 무엇입니까?

이러한 언어가 메모리를 관리 할 가치가있는 유일한 리소스로 간주하는 것은 매우 부족합니다. 소켓, 파일 핸들, 응용 프로그램 상태는 어떻습니까?

kdbanman의 답변에 동의하지 않습니다 . 사실은 참조 카운트에 대해 매우 치우 치지 만 대부분 정확하다고 말했지만, 그들이 문제에 대해 불만을 제기 한 상황을 제대로 설명하지는 않습니다.

나는 그 대답에서 발전된 용어가 큰 문제라고 생각하지 않으며, 혼동을 일으킬 가능성이 더 높습니다. 실제로, 제시된 바와 같이, 용어는 대부분 절차가 수행되는 것이 아니라 절차가 활성화되는 방식에 의해 결정된다. 요점은 모든 경우에 더 이상 정리 프로세스에서 더 이상 필요하지 않은 개체를 마무리하고 사용중인 모든 리소스를 해제해야한다는 것입니다. 메모리는 그 중 하나 일뿐입니다. 이상적으로는 가비지 수집기를 사용하여 개체를 더 이상 사용하지 않을 때 모든 작업을 자동으로 수행해야합니다. 실제로, GC가 없거나 결함이있을 수 있으며, 이는 마무리 및 교정 프로그램에 의한 명시 적 트리거링에 의해 보상됩니다.

프로그램에 의한 명시 적 심사는 여전히 사용중인 객체가 명시 적으로 종료 될 때 프로그래밍 오류를 분석하기가 어렵 기 때문에 문제가됩니다.

따라서 자동 가비지 수집을 사용하여 리소스를 회수하는 것이 훨씬 좋습니다. 그러나 두 가지 문제가 있습니다.

  • 일부 가비지 수집 기술은 리소스 누수를 허용하는 메모리 누수를 허용합니다. 이것은 참조 카운팅 GC로 잘 알려져 있지만,주의하지 않고 일부 데이터 조직을 사용할 때 다른 GC 기술에서 나타날 수 있습니다 (여기서는 설명하지 않음).

  • GC 기술은 더 이상 사용되지 않는 메모리 리소스를 식별하는 데 능숙 할 수 있지만 여기에 포함 된 객체를 마무리하는 것은 간단하지 않을 수 있으며 이러한 객체가 사용하는 다른 리소스를 회수하는 문제를 복잡하게 만드는 경우가 많으며 이는 종종 마무리의 목적입니다.

마지막으로, 종종 잊어 버린 중요한 점은 적절한 후크가 제공되고 GC주기의 비용이 가치가 있다고 생각되면 메모리 부족뿐만 아니라 GC주기가 트리거 될 수 있다는 것입니다. 따라서 어떤 종류의 자원이 없어지면 일부 자원을 확보하기 위해 GC를 시작하는 것이 좋습니다.

참조 카운트 가비지 수집기

참조 카운트는 약한 가비지 수집 기술로 사이클을 제대로 처리하지 못합니다. 쓸모없는 구조를 파괴하고 메모리를 되 찾는 데 약하기 때문에 다른 리소스를 되 찾는 데 실제로 약한 것입니다. 그러나 참조 횟수 GC는 참조 횟수가 0으로 내려갈 때 구조를 회수하므로 주소를 정적으로 또는 유형과 함께 알고 있기 때문에 파이널 라이저는 참조 카운트 가비지 수집기 (GC)와 함께 가장 쉽게 사용할 수 있습니다. 또는 동적으로. 따라서 적절한 종료자를 적용하고 모든 지정된 객체에 대해 프로세스를 재귀 적으로 호출 한 후 (마무리 절차를 통해) 메모리를 정확하게 회수 할 수 있습니다.

간단히 말해서, Ref Counting GC를 사용하면 최종화를 쉽게 구현할 수 있지만 실제로 원형 구조로 인해 GC의 "불완전 성"으로 인해 메모리 교정이 겪는 것과 정확히 동일하게됩니다. 다시 말해, 참조 카운트를 사용 하면 소켓, 파일 핸들 등과 같은 다른 리소스만큼 메모리가 정확하게 관리되지 않습니다 .

실제로 Ref Count GC가 루핑 구조 (일반적으로)를 되 찾을 수없는 것은 메모리 누수로 보일 수 있습니다 . 모든 GC가 메모리 누수를 피할 것으로 기대할 수는 없습니다. GC 알고리즘 및 동적으로 사용 가능한 유형 구조 정보 (예 : 보수적 GC )에 따라 다릅니다 .

가비지 수집기 추적

이러한 누수가없는보다 강력한 GC 제품군은 잘 식별 된 루트 포인터에서 시작하여 메모리의 실제 부분을 탐색하는 추적 제품군 입니다. 이 추적 프로세스에서 방문하지 않은 메모리의 모든 부분 (실제로는 여러 가지 방법으로 분해 할 수 있지만 단순화해야 함)은 메모리의 사용되지 않은 부분이므로 1 을 회수 할 수 있습니다 . 이러한 수집기는 프로그램이 수행하는 방식에 관계없이 더 이상 프로그램에서 액세스 할 수없는 모든 메모리 부분을 회수합니다. 그것은 원형 구조를 되찾고, 더 진보 된 GC는이 패러다임의 변형을 기반으로하며 때로는 고도로 정교합니다. 경우에 따라 참조 횟수와 결합하여 약점을 보완 할 수 있습니다.

문제는 당신의 진술이 (질문의 끝에서) 있다는 것입니다.

자동 가비지 콜렉션을 제공하는 언어는 오브젝트가 더 이상 사용되지 않을 때 100 % 확실하게 알기 때문에 오브젝트 파괴 / 완료를 지원하는 주요 후보로 보입니다.

콜렉터 추적에 기술적으로 올바르지 않습니다 .

100 % 확실성으로 알려진 것은 더 이상 사용되지 않는 메모리 부분입니다 . 더 정확하게 는 프로그램의 논리에 따라 더 이상 사용할 수없는 일부 부분이 여전히 프로그램에 쓸모없는 포인터가있는 경우 여전히 사용중인 것으로 간주되기 때문에 더 이상 액세스 할 수 없다고 말해야합니다 그러나 현재 사용되지 않는 메모리 부분에 사용되지 않은 개체 가 저장되어 있는지 확인하려면 추가 처리와 적절한 구조가 필요합니다 . 프로그램이 더 이상 메모리의 이러한 부분에 연결되어 있지 않기 때문에 프로그램에서 알려진 내용을 확인할 수 없습니다.

따라서 가비지 콜렉션을 통과 한 후에는 더 이상 사용하지 않는 객체를 포함하는 메모리 조각이 남지만 올바른 마무리를 적용하기 위해 이러한 객체가 무엇인지 알 수있는 방법은 없습니다. 또한 추적 수집기가 마크 앤 스윕 유형 인 경우 일부 조각에 이전 GC 패스에서 이미 마무리되었지만 조각화 이유로 사용되지 않은 개체가 포함되어있을 수 있습니다. 그러나 이것은 확장 된 명시 적 타이핑을 사용하여 처리 할 수 ​​있습니다.

간단한 수집기는 추가 메모리없이 이러한 메모리 조각을 회수하기 만하지만, 마무리에는 사용되지 않은 메모리를 탐색하고 포함 된 개체를 식별하고 마무리 절차를 적용하기위한 특정 단계가 필요합니다. 그러나 그러한 탐구에는 거기에 저장된 객체의 유형을 결정해야하며, 적절한 결정을 적용하려면 유형 결정도 필요합니다.

따라서 다양한 기술로 GC 시간에 추가 비용 (추가 패스)과 추가 메모리 비용으로 해당 유형 정보를 제공 할 수 있습니다. 시간과 공간의 오버 헤드가 모든 개체와 관련 될 수있는 반면, 종종 몇 가지 개체 만 마무리하기를 원하므로 이러한 비용이 상당 할 수 있습니다.

또 다른 요점은 시간 및 공간 오버 헤드가 GC 실행뿐만 아니라 프로그램 코드 실행과 관련 될 수 있다는 것입니다.

나는 당신이 열거 한 많은 언어들의 세부 사항을 알지 못하기 때문에 특정 문제를 지적하면서 더 정확한 대답을 줄 수 없습니다. C의 경우 타이핑은 매우 어려운 문제이며 보수적 인 수집가의 개발로 이어집니다. 내 생각 엔 이것이 C ++에도 영향을 미치지 만 C ++에 대한 전문가는 아닙니다. 이는 보수적 GC에 대한 많은 연구를 수행 한 Hans Boehm에 의해 확인 된 것으로 보인다 . 보수적 GC는 데이터에 대한 정확한 유형 정보가 없기 때문에 사용되지 않은 모든 메모리를 체계적으로 회수 할 수 없습니다. 같은 이유로 마무리 절차를 체계적으로 적용 할 수 없습니다.

따라서 일부 언어에서 알 수 있듯이 원하는 것을 할 수 있습니다. 그러나 그것은 무료로 오지 않습니다. 언어 및 구현에 따라 기능을 사용하지 않더라도 비용이 발생할 수 있습니다. 이러한 문제를 해결하기 위해 다양한 기술과 장단점이 고려 될 수 있지만 이는 합당한 규모의 답변 범위를 벗어납니다.

1-이것은 추적 콜렉션 (복사 및 표시 및 스위프 GC를 모두 포함)의 추상 프리젠 테이션이며, 추적 콜렉터의 유형에 따라 상황이 다르며, 복사 또는 표시 여부에 따라 메모리의 사용되지 않은 부분 탐색이 다릅니다. 스윕이 사용됩니다.


가비지 수집에 대한 많은 세부 정보를 제공합니다. 그러나 귀하의 답변은 실제로 내 의견에 동의하지 않습니다. 초록과 내 TLDR은 본질적으로 동일한 것을 말합니다. 그리고 가치가있는 것에 대해, 제 대답은 "강한 편견"이 아닌 기준 계수 GC를 예로 사용합니다.
kdbanman 2019

더 자세히 읽은 후에는 의견이 맞지 않습니다. 그에 따라 편집하겠습니다. 또한, 나의 용어는 모호하지 않아야했다. 문제는 파이널 라이저와 소멸자를 혼란스럽게 만들고 동일한 호흡에서 디스 포저를 언급하기까지했습니다. 올바른 단어를 전파하는 것이 가치가 있습니다.
kdbanman

@kdbanman 어려운 점은 귀하의 답변이 참조로 서 있기 때문에 두 사람 모두에게 연설하는 것이 었습니다. ref count는 약한 GC이므로 언어에 거의 사용되지 않으며 (OP에서 인용 한 언어 확인) 실제로 종료자를 추가하는 것이 쉽지만 (제한된 사용으로) 패러다임 예제로 사용할 수 없습니다. 추적 수집기는 거의 항상 사용됩니다. 그러나 죽어가는 객체는 알려져 있지 않기 때문에 마무리 도구를 사용하기가 어렵습니다. 데이터 저장소의 동적 유형 지정이 필수적이므로 정적 유형과 동적 유형의 구분은 중요하지 않습니다.
babou

@kdbanman 용어와 관련하여 일반적으로 다른 상황에 해당하므로 유용합니다. 그러나 최종화를 GC로 이전하는 것에 관한 문제이기 때문에 여기서는 도움이되지 않습니다. 기본 GC는 파괴 만해야합니다. 필요한 것은 구별하는 용어입니다 getting memory recycled내가 전화 reclamation하고, 일을 몇 가지 정리 그 전에, 기타 자원을 회수 또는 내가 전화하는 오브젝트의 테이블을 업데이트하는 등 finalization. 이것들은 나에게 관련 문제로 보였지만, 나는 당신의 용어에서 나에게 새로운 점을 놓쳤을 수도 있습니다.
babou

1
감사합니다 @kdbanman, babou. 좋은 토론. 두 게시물 모두 비슷한 점을 다루고 있다고 생각합니다. 둘 다 지적했듯이 핵심 문제는 언어 런타임에 사용되는 가비지 수집기 범주 인 것으로 보입니다. 나는 이 기사를 발견했다 . 보다 강력한 gcs는 낮은 수준의 원시 메모리 만 처리하므로 더 높은 수준의 객체 유형이 gc에 불투명하게됩니다. 메모리 내부에 대한 지식이 없으면 gc는 객체를 파괴 할 수 없습니다. 어느 것이 당신의 결론 인 것 같습니다.
dbcb

4

객체 소멸자 패턴은 시스템 프로그래밍에서 오류 처리의 기본이지만 가비지 수집과는 관련이 없습니다. 오히려 객체 수명을 범위에 일치시키는 것과 관련이 있으며 일급 함수가있는 모든 언어로 구현 / 사용할 수 있습니다.

예 (의사 코드). Posix 파일 디스크립터 유형과 같은 "원시 파일"유형이 있다고 가정하십시오. 네 가지 기본적인 작업이 있습니다, open(), close(), read(), write(). 항상 자체적으로 정리되는 "안전한"파일 형식을 구현하려고합니다. (즉, 자동 생성자와 소멸자가 있습니다.)

나는 우리의 언어와 예외 처리를 가지고 가정합니다 throw, try그리고 finally(당신이 유형의 사용자가 오류를 표시하는 특별한 값을 반환하는 훈련을 설정할 수 있습니다 처리 예외없이 언어.)

작업을 수행하는 기능을 허용하는 기능을 설정합니다. 작업자 함수는 하나의 인수 ( "안전한"파일에 대한 핸들)를 허용합니다.

with_file_opened_for_read (string:   filename,
                           function: worker_function(safe_file f)):
  raw_file rf = open(filename, O_RDONLY)
  if rf == error:
    throw File_Open_Error

  try:
    worker_function(rf)
  finally:
    close(rf)

또한 구현 제공 read()write()대한 safe_file(단지 부르는 raw_file read()등을 write()). 이제 사용자는 다음 safe_file과 같은 유형을 사용합니다 .

...
with_file_opened_for_read ("myfile.txt",
                           anonymous_function(safe_file f):
                             mytext = read(f)
                             ... (including perhaps throwing an error)
                          )

C ++ 소멸자는 실제로 try-finally블록에 대한 구문 설탕입니다 . 여기서 내가 한 거의 모든 safe_file것은 생성자와 소멸자가 있는 C ++ 클래스를 컴파일하는 것입니다. C ++에는 finally예외 가 없습니다 . 특히 Stroustrup은 명시 적 소멸자를 사용하는 것이 구문 적으로 더 효과적이라고 생각했기 때문에 (언어가 익명 함수를 갖기 전에 언어에 도입했습니다).

(이것은 사람들이 Lisp와 같은 언어로 수년간 오류 처리를해온 방법 중 하나를 단순화 한 것입니다. 1980 년대 후반이나 1990 년대 초반에 처음 봤지만 어디에 있는지 기억이 나지 않습니다.)


이것은 C ++에서 스택 기반 소멸자 패턴의 내부를 설명하지만 가비지 수집 언어가 이러한 기능을 구현하지 않는 이유는 설명하지 않습니다. 이것이 가비지 수집과 관련이 없다고 생각할 수도 있지만, 가비지 수집 언어에서는 어렵거나 비효율적 인 것으로 보이는 일반적인 객체 제거 / 완료와 관련이 있습니다. 따라서 일반적인 파괴가 지원되지 않으면 스택 기반 파괴도 생략 된 것으로 보입니다.
dbcb

내가 처음에 말했듯이 : 일급 함수 (또는 일급 함수의 근사)를 갖는 가비지 수집 언어는 다음과 같은 "총알 증거"인터페이스를 제공하는 기능을 제공합니다 safe_filewith_file_opened_for_read그 범위를 벗어나면 그 자체를 닫습니다 (객체 ). 중요한 것은 생성자와 같은 구문을 가지고 있지 않다는 것입니다. Lisp, Scheme, Java, Scala, Go, Haskell, Rust, Javascript, Clojure는 모두 충분한 일급 함수를 지원하므로 동일한 유용한 기능을 제공하기 위해 소멸자가 필요하지 않습니다.
방황 논리

무슨 말인지 알 것 같아 언어는 소멸자와 같은 기능을 수동으로 구현하기위한 기본 빌딩 블록 (시도 / 캐치 / 최종, 일등 함수 등)을 제공하므로 소멸자가 필요하지 않습니까? 간결함을 이유로 일부 언어가 해당 경로를 사용하는 것을 볼 수있었습니다. 비록 그것이 나열된 모든 언어의 주된 이유 인 것 같지는 않지만 아마도 그것이 그럴 것입니다. 어쩌면 나는 C ++ 소멸자를 사랑하고 아무도 신경 쓰지 않는 방대한 소수에 속할 것입니다. 이는 대부분의 언어가 소멸자를 구현하지 않는 이유 일 수 있습니다. 그들은 신경 쓰지 않습니다.
dbcb

2

이것은 질문에 대한 완전한 대답은 아니지만 다른 답변이나 의견에서 다루지 않은 몇 가지 관찰 사항을 추가하고 싶었습니다.

  1. 이 질문은 우리가 Simula 스타일 객체 지향 언어에 대해 이야기하고 있다고 암시 적으로 가정합니다. 대부분의 언어에서 모든 객체가 아닌 객체를 가진 언어조차도 객체입니다. 소멸자를 구현하는 기계는 모든 언어 구현자가 지불 할 의사가없는 비용을 부과합니다.

  2. C ++은 파기 순서에 대한 암시 적 보증을합니다. 예를 들어 트리와 같은 데이터 구조가있는 경우 부모보다 자식이 파괴됩니다. GC 언어에서는 그렇지 않으므로 계층 적 리소스가 예측할 수없는 순서로 해제 될 수 있습니다. 비 메모리 리소스의 경우 문제가 될 수 있습니다.


2

가장 널리 사용되는 두 가지 GC 프레임 워크 (Java 및 .NET)를 설계 할 때 저자는 다른 형태의 자원 관리가 필요하지 않을 정도로 마무리 작업이 충분히 효과적이라고 생각합니다. 100 % 신뢰할 수 있고 결정적인 리소스 관리를 수용하는 데 필요한 모든 기능이 필요하지 않은 경우 언어 및 프레임 워크 디자인의 여러 측면을 크게 단순화 할 수 있습니다. C ++에서는 다음과 같은 개념을 구분해야합니다.

  1. 참조 소유자가 독점적으로 소유하고 소유자가 모르는 포인터 / 참조로 식별되지 않는 객체를 식별하는 포인터 / 참조.

  2. 다른 사람이 독점적으로 소유하지 않는 공유 가능 객체를 식별하는 포인터 / 참조.

  3. 참조 보유자 가 독점적으로 소유 하지만 "보기"를 통해 액세스 할 수 있는 오브젝트를 식별하는 포인터 / 참조 는 소유자가 추적 할 방법이 없습니다.

  4. 다른 사람이 소유 한 객체를 볼 수있는 객체를 식별하는 포인터 / 참조.

GC 언어 / 프레임 워크가 리소스 관리에 대해 걱정할 필요가없는 경우 위의 모든 것을 단일 종류의 참조로 대체 할 수 있습니다.

나는 마무리가 다른 형태의 자원 관리에 대한 필요성을 제거 할 것이라는 생각은 알지 못했지만, 당시에 그러한 기대가 합리적 이었는지 여부에 관계없이, 역사는 마무리가 제공하는 것보다 더 정확한 자원 관리를 필요로하는 많은 사례가 있음을 보여 주었다 . 언어 / 프레임 워크 수준에서 소유권을 인정하면 보상이 비용을 정당화하기에 충분하다고 생각합니다 (복잡성이 어딘가에 존재해야하며이를 언어 / 프레임 워크로 옮기면 사용자 코드가 단순화 될 것임). 언어 / 프레임 워크가 자원 정리 문제와 무관 한 경우에만 작동하는 단일 "종류"의 참조가 있으면 설계 이점이 있습니다.


2

가비지 수집 언어의 객체 소멸자 패러다임이 널리 보급되지 않은 이유는 무엇입니까?

나는 C ++ 배경에서 왔 으므로이 지역은 당황 스럽습니다.

C ++의 소멸자는 실제로 가지 작업을 수행합니다. 결합합니다. RAM을 비우고 리소스 ID를 비 웁니다.

다른 언어는 분리 GC가 자원 ID를 해제의 다른 언어 기능 걸릴 충전하는 동안 RAM을 자유롭게 담당함으로써 이러한 우려를.

이러한 언어가 메모리를 관리 할 가치가있는 유일한 리소스로 간주하는 것은 매우 부족합니다.

그것이 바로 GC가하는 일입니다. 그들은 한 가지 일 을하지 않으며 메모리가 부족하지 않도록하는 것입니다. RAM이 무한한 경우 모든 GC는 더 이상 존재하지 않는 실제 사유가 없으므로 폐기됩니다.

소켓, 파일 핸들, 응용 프로그램 상태는 어떻습니까?

언어는 다음과 같은 방법으로 리소스 ID를 해제하는 다양한 방법을 제공 할 수 있습니다.

  • .CloseOrDispose()코드에 흩어져있는 수동

  • 수동 .CloseOrDispose()" finally블록" 내에 흩어져있는 수동

  • 수동 "리소스 ID 블록"(즉 using, with, try가진 - - 자원 자동화 등) .CloseOrDispose()블록이 후 수행

  • .CloseOrDispose()블록이 완료된자동화되는 "자원 ID 블록"보장

많은 언어가 리소스를 잘못 관리 할 수있는 기회를 제공하는 수동 메커니즘 (보증이 아닌)을 사용합니다. 이 간단한 NodeJS 코드를 사용하십시오.

require('fs').openSync('file1.txt', 'w');
// forget to .closeSync the opened file

.. 프로그래머가 열린 파일을 닫는 것을 잊어 버린 곳.

프로그램이 계속 실행되는 동안 열린 파일은 림보에 멈춰 있습니다. HxD를 사용하여 파일을 열려고 시도 할 수 없는지 확인하면 쉽게 확인할 수 있습니다.

여기에 이미지 설명을 입력하십시오

C ++ 소멸자 내에서 자원 ID를 해제하는 것도 보장되지 않습니다. RAII가 보장 된 "리소스 ID 블록"처럼 작동하지만 "리소스 ID 블록"과 달리 C ++ 언어는 RAII 블록을 제공하는 객체가 누수 되는 것을 막지 않으므로 RAII 블록은 절대 수행 되지 않을 수 있습니다 .


Ruby, Javascript / ES6 / ES7, Actionscript, Lua 등과 같은 OOPy 객체 지원을 통해 거의 모든 현대 가비지 수집 언어가 소멸자 / 최종 패러다임을 완전히 생략합니다. 파이썬은 클래스 __del__()메소드를 가진 유일한 것 같습니다 . 왜 이런거야?

위에서 언급 한 것처럼 다른 방법으로 리소스 ID를 관리하기 때문입니다.

이러한 언어가 객체 처리에 대한 사용자 지정 논리를 실행할 방법이없는 언어 설계 결정은 무엇입니까?

위에서 언급 한 것처럼 다른 방법으로 리소스 ID를 관리하기 때문입니다.

왜 언어가 클래스 또는 클래스와 유사한 구조를 가진 객체 인스턴스에 대한 개념을 사용자 정의 인스턴스화 (생성자)와 함께 가지고 있지만 파괴 / 완료 기능을 완전히 생략하는 이유는 무엇입니까?

위에서 언급 한 것처럼 다른 방법으로 리소스 ID를 관리하기 때문입니다.

소멸자 / 완료자가 미래에 결정되지 않은 시간이 될 때까지 호출되지 않을 수도 있지만 Java 또는 Python이 기능을 지원하는 것을 막지 않았다는 가능한 주장을 알 수 있습니다.

Java에는 소멸자가 없습니다.

자바 문서는 언급 :

그러나 마무리의 일반적인 목적은 개체를 취소 할 수없는 상태로 폐기하기 전에 정리 작업을 수행하는 것입니다. 예를 들어, 입출력 연결을 나타내는 오브젝트의 finalize 메소드는 오브젝트가 영구적으로 삭제되기 전에 명시 적 I / O 트랜잭션을 수행하여 연결을 끊을 수 있습니다.

.. 그러나 자원 ID 관리 코드를 넣는 Object.finalizer것은 크게 반 패턴으로 간주됩니다 ( cf. ). 이러한 코드는 대신 호출 사이트에서 작성해야합니다.

안티 패턴을 사용하는 사람들의 경우 , 호출 사이트에서 리소스 ID를 해제하는 것을 잊었을 수 있습니다. 따라서, 경우에 따라서 는 파이널 라이저에서 다시 수행합니다 .

어떤 형태의 객체 마무리를 지원하지 않는 핵심 언어 설계 이유는 무엇입니까?

더 이상 객체에 대한 강한 참조가없는 시간과 GC가 메모리를 회수하는 시간 사이에 코드 조각을 실행하기 때문에 파이널 라이저의 사용 사례는 많지 않습니다.

가능한 사용 사례는 GC에 의해 객체가 수집되는 시간과 더 이상 객체에 대한 강한 참조가없는 시간의 로그를 유지하려는 경우입니다.

finalize() {
    Log(TimeNow() + ". Obj " + toString() + " is going to be memory-collected soon!"); // "soon"
}

-1

Dr Dobbs wrt c ++에서 소멸자가 구현되는 언어에서 문제가 있다고 주장하는보다 일반적인 아이디어가있는 참조를 발견했습니다. 대략적인 아이디어는 소멸자의 주요 목적이 메모리 할당 해제를 처리하는 것이며 올바르게 수행하기가 어렵다는 것 같습니다. 메모리는 조각 단위로 할당되지만 다른 객체가 연결되고 할당 해제 책임 / 경계가 명확하지 않습니다.

따라서 가비지 수집기의 솔루션은 몇 년 전에 진화했지만 가비지 수집은 메서드 종료시 범위에서 사라지는 객체를 기반으로하지 않지만 (구현하기 어려운 개념적인 아이디어), 주기적으로 다소 비 결정적으로 실행되는 수집기, 앱에 "메모리 압력"이 발생하는 경우 (예 : 메모리 부족)

다시 말해, "새로 사용되지 않은 객체"라는 단순한 인간 개념은 실제로 어떤 객체도 "즉시"사용되지 않을 수 없다는 점에서 오해의 소지가있는 추상화입니다. 사용되지 않은 객체는 객체 참조 그래프를 가로 지르는 가비지 수집 알고리즘을 실행하여 "발견"할 수 있으며 최고 성능의 알고리즘은 간헐적으로 실행됩니다.

사용하지 않는 객체를 거의 즉시 식별 할 수있는 더 나은 가비지 수집 알고리즘이 발견되기를 기다리고있을 수 있으며, 이로 인해 일관된 소멸자 호출 코드로 이어질 수 있지만, 해당 지역에서 수년간의 연구 끝에 발견되지 않았습니다.

파일이나 연결과 같은 자원 관리 영역에 대한 솔루션은 사용을 처리하려고 시도하는 객체 "관리자"가있는 것 같습니다.


2
재미있는 발견. 감사. 저자의 주장은 클래스에 적절한 복사 생성자가없는 실제 인스턴스 인 클래스 인스턴스를 값으로 전달하여 소멸자가 잘못 호출 된 것을 기반으로합니다. 그러나이 시나리오는 대부분의 현대 동적 언어에는 존재하지 않습니다. 모든 것이 참조로 전달되므로 저자의 상황을 피할 수 있기 때문입니다. 이것은 흥미로운 관점이지만, 대부분의 가비지 수집 언어가 소멸자 / 완료 기능을 생략하기 위해 선택한 이유는 설명하지 않습니다.
dbcb

2
이 답변은 Dr. Dobb의 기사를 잘못 나타냅니다.이 기사는 소멸자가 일반적으로 문제가 있다고 주장하지 않습니다. 이 기사는 사실이 주장 : 그들은 간단하지만 너무 강력 모두 있기 때문에 메모리 관리의 기본 요소는, 고토 문 같다. goto 문이 "적절하게 제한된 제어 구조"(Dijktsra 참조)에 가장 잘 캡슐화되는 것과 마찬가지로 메모리 관리 프리미티브는 "적절하게 제한된 데이터 구조"에 가장 잘 캡슐화됩니다. 소멸자는이 방향으로 한 걸음이지만 충분하지 않습니다. 그것이 사실인지 아닌지 스스로 결정하십시오.
kdbanman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.