Java 개발자가 의식적으로 RAII를 포기 했습니까?


82

오랜 C # 프로그래머 인 저는 최근에 RAII ( Resource Acquisition Is Initialization) 의 장점에 대해 더 많이 알게되었습니다 . 특히 C # 관용구를 발견했습니다.

using (var dbConn = new DbConnection(connStr)) {
    // do stuff with dbConn
}

C ++에 해당합니다.

{
    DbConnection dbConn(connStr);
    // do stuff with dbConn
}

C ++ DbConnection에서는 using블록 에서 와 같이 리소스를 사용하는 것을 기억하는 것이 불필요 하다는 것을 의미합니다 ! 이것은 C ++의 주요 이점으로 보입니다. DbConnection예를 들어 유형이 인스턴스 멤버 인 클래스를 고려할 때 더욱 설득력이 있습니다.

class Foo {
    DbConnection dbConn;

    // ...
}

C #에서는 Foo IDisposable를 다음과 같이 구현해야합니다 .

class Foo : IDisposable {
    DbConnection dbConn;

    public void Dispose()
    {       
        dbConn.Dispose();
    }
}

더 나쁜 것은, 모든 사용자가 Foo둘러싸 기억해야합니다 FooA의 using같은 블록 :

   using (var foo = new Foo()) {
       // do stuff with "foo"
   }

이제 C #과 Java 루트를보고 궁금합니다 ... Java 개발자는 힙을 위해 스택을 포기했을 때 무엇을 포기했는지 RAII를 포기 했습니까?

유사하게 Stroustrup 은 RAII의 중요성을 충분히 인식 했습니까?


5
C ++에서 리소스를 묶지 않는 것에 대해 이야기하고있는 것이 확실하지 않습니다. DBConnection 객체는 소멸자의 모든 리소스 닫기를 처리합니다.
maple_shaft

16
@maple_shaft, 정확히 내 요점! 이것이이 질문에서 다루고있는 C ++의 장점입니다. C #에서는 리소스를 "사용 중"으로 묶어야합니다. C ++에서는 그렇지 않습니다.
JoelFan

12
내 이해는 전략으로서 RAII가 C ++ 컴파일러가 실제로 고급 템플릿을 사용하기에 충분할 때만 이해되었다는 것이다. 자바를 만들 때 사용하기 위해 실제로 사용할 수 있었던 C는 ++로, 스타일은 매우 원시적 인, "클래스와 C"이었다 어쩌면 당신이 운이 있다면, 기본 템플릿.
Sean McMillan

6
"나의 이해는 전략으로서 RAII가 C ++ 컴파일러가 실제로 고급 템플릿을 사용할 수있을 정도로만 잘 이해되고 있다는 것입니다. -맞지 않아요. 생성자와 소멸자는 첫날부터 C ++의 핵심 기능으로, 템플릿을 널리 사용하기 전과 Java보다 훨씬 앞서 있습니다.
Jim In Texas

8
@ JimInTexas : Sean은 어딘가에 기본적인 진실의 씨앗을 가지고 있다고 생각합니다 (템플릿은 아니지만 예외는 요점입니다). 처음부터 생성자 / 소멸자가 있었지만, 중요성과 RAII의 개념은 처음에 (내가 찾고있는 단어) 실현되지 않았습니다. RAII 전체가 얼마나 중요한지를 깨닫기 전에 컴파일러가 좋은 결과를 얻는 데 몇 년과 시간이 걸렸습니다.
Martin York

답변:


38

이제 C #과 Java 루트를보고 궁금합니다 ... Java 개발자는 힙을 위해 스택을 포기했을 때 무엇을 포기했는지 RAII를 포기 했습니까?

유사하게 Stroustrup은 RAII의 중요성을 충분히 인식 했습니까?

Java를 디자인 할 때 Gosling이 RAII의 중요성을 얻지 못했다고 확신합니다. 인터뷰에서 그는 제네릭과 연산자 오버로딩을 생략하는 이유에 대해 이야기했지만 결정 론적 소멸자와 RAII는 언급하지 않았습니다.

재미 있지만 Stroustrup조차도 결정 론적 소멸자가 설계 당시의 중요성을 알지 못했습니다. 나는 인용문을 찾을 수 없지만 실제로 인용하면 그의 인터뷰에서 찾을 수 있습니다 : http://www.stroustrup.com/interviews.html


11
@maple_shaft : 요컨대 불가능합니다. 결정 론적 가비지 콜렉션 (일반적으로 불가능하고 어쨌든 지난 수십 년 동안 모든 GC 최적화를 무효화하는 방법)을 발명 한 경우를 제외하고는 스택 할당 된 오브젝트를 도입해야하지만 몇 가지 캔이 열립니다. 웜 : 이러한 개체에는 의미 체계가 필요합니다. 하위 유형 지정 (따라서 다형성이 없음)의 "슬라이스 문제", 중요한 제한을 두거나 호환되지 않는 유형 시스템을 변경하지 않는 한 포인터가 매달려 있습니다. 그리고 그것은 내 머리 꼭대기에 있습니다.

13
@DeadMG : 따라서 수동 메모리 관리로 돌아가는 것이 좋습니다. 그것은 일반적으로 프로그래밍에 대한 유효한 접근법이며, 물론 결정 론적 파괴를 허용합니다. 그러나 이것은 우리가 바보처럼 행동하더라도 메모리 안전과 잘 정의 된 동작을 제공하고자하는 GC 전용 설정과 관련이있는이 질문에 대한 답은 아닙니다. 그것은 모든 것을 위해 GC가 필요하고 객체 파괴를 수동으로 시작하는 방법이 없으며 ( 존재하는 모든 Java 코드는 적어도 이전 코드에 의존합니다), GC를 결정 론적으로 만들거나 운이 좋지 않습니다.

26
@delan. C ++ 스마트 포인터 manual메모리 관리를 호출하지 않습니다 . 그들은 결정 론적 미세 입자 제어 가비지 수집기와 비슷합니다. 올바르게 사용하면 똑똑한 포인터가 꿀벌의 무릎입니다.
Martin York

10
@LokiAstari : 글쎄, 그것들은 풀 GC보다 약간 덜 자동적이며 (실제로 어떤 종류의 똑똑 함을 생각 해야하는지) 라이브러리를 구현하려면 원시 포인터가 필요하므로 수동 메모리 관리가 필요합니다. . 또한 순환 참조를 자동으로 처리하는 것보다 스마트 포인터를 알지 못합니다. 이는 내 책의 가비지 수집에 대한 엄격한 요구 사항입니다. 스마트 포인터는 확실히 엄청나게 시원하고 유용하지만 완전하고 독점적 인 GC 언어에 대한 보장을 제공 할 수는 없습니다.

11
@ delan : 나는 거기에 동의해야합니다. 결정적이므로 GC보다 자동이라고 생각합니다. 승인. 효율적으로 사용하려면 올바른 것을 사용해야합니다 (나는 그것을 줄 것입니다). std :: weak_ptr은 사이클을 완벽하게 처리합니다. 사이클은 항상 트로트되지만 기본 오브젝트는 일반적으로 스택 기반이므로 실제로는 거의 문제가되지 않습니다. 드문 경우이지만 std :: weak_ptr 문제 일 수 있습니다.
Martin York

60

그렇습니다. C #의 디자이너 (그리고 Java)는 결정 론적 마무리를 결정하지 않았습니다. 나는 Anders Hejlsberg에게 1999-2002 년에 여러 번이 문제를 물었습니다.

첫째, 스택 기반 또는 힙 기반 여부에 따라 객체에 대한 서로 다른 의미론에 대한 아이디어는 확실히 두 언어의 통합 디자인 목표와 상반되므로 프로그래머가 정확히 그러한 문제를 해결하는 것이 었습니다.

둘째, 장점이 있음을 인정하더라도 부기에는 상당한 구현 복잡성과 비 효율성이 있습니다. 실제로 스택과 같은 객체를 관리되는 언어로 스택에 넣을 수는 없습니다 . "스택과 같은 의미"라고 말하고 상당한 작업을 수행해야합니다 (값 유형은 이미 충분히 어렵습니다. 참조가 들어오고 관리되는 메모리로 돌아가는 복잡한 클래스의 인스턴스 인 객체에 대해 생각하십시오).

따라서 "(거의) 모든 것이 객체"인 프로그래밍 시스템의 모든 객체에 대해 결정론적인 마무리를 원하지 않습니다. 그래서 당신은 결정적 마무리를 가지고 하나에서 정상 추적 개체를 분리하는 프로그래머 제어 구문의 어떤 종류를 소개합니다.

C #에는 using키워드가 있는데 C # 1.0이 된 디자인에서 상당히 늦었습니다. 모든 IDisposable것이 꽤 비참 하며 보일러 플레이트 패턴을 자동으로 적용 할 수 있는 클래스를 표시하는 usingC ++ 소멸자 구문으로 작업하는 것이 더 우아 할까요?~IDisposable


2
관리되는 힙의 개체에도 RIAA를 제공하는 스택 기반 "핸들"이있는 C ++ / CLI (.NET)가 수행 한 작업은 어떻습니까?
JoelFan

3
C ++ / CLI에는 매우 다른 디자인 결정 및 제약 조건이 있습니다. 이러한 결정 중 일부는 프로그래머가 메모리 할당 및 성능에 미치는 영향에 대해 더 많은 생각을 요구할 수 있음을 의미합니다. 그리고 저는 C ++ / CLI 컴파일러가 C # (특히 초기 세대)보다 훨씬 더 복잡하다고 생각합니다.
래리 오브라이언

5
+1 이것은 정답입니다. Java에는 의도적으로 (기본이 아닌) 스택 기반 객체가 없기 때문입니다.
BlueRaja-대니 Pflughoeft

8
@Peter Taylor-맞습니다. 그러나 C #의 비 결정적 소멸자는 제한적인 리소스를 관리하는 데 의존 할 수 없기 때문에 가치가 거의 없다고 생각합니다. 제 생각에는 ~구문을 사용하여 구문 설탕 으로 사용하는 것이 좋습니다.IDisposable.Dispose()
Larry OBrien

3
@Larry : 동의합니다. C ++ / CLI 는에~ 대한 구문 설탕으로 사용 되며 IDisposable.Dispose()C # 구문보다 훨씬 편리합니다.
dan04

41

Java는 C ++이 매우 다른 언어였던 1991-1995 년에 개발되었다는 것을 명심하십시오. 예외 (RAII가 필요함 )와 템플릿 (스마트 포인터를 쉽게 구현할 수있게 해주는)은 "새로운 기능"기능이었습니다. 대부분의 C ++ 프로그래머는 C에서 왔으며 수동 메모리 관리에 익숙했습니다.

따라서 Java 개발자가 고의로 RAII를 포기하기로 결정한 것이 의심됩니다. 그러나 Java가 가치 의미론 대신 참조 의미론을 선호하는 것은 의도적 인 결정이었습니다. 결정 론적 파괴는 참조 의미 론적 언어로 구현하기가 어렵다.

그렇다면 왜 가치 의미론 대신 참조 의미론을 사용합니까?

언어 가 훨씬 단순 해지기 때문입니다.

  • 사이 통 사적 구분이 필요 없다 FooFoo*나 사이 foo.barfoo->bar.
  • 모든 할당이 포인터를 복사하는 경우 오버로드 할당이 필요하지 않습니다.
  • 복사 생성자가 필요하지 않습니다. ( 때때로 와 같은 명시적인 복사 기능이 필요합니다 clone(). 많은 객체를 복사 할 필요는 없습니다. 예를 들어, 불변은 그렇지 않습니다.)
  • private복사 생성자 를 선언 operator=하거나 클래스를 복사 할 수 없도록 만들 필요는 없습니다 . 클래스의 객체를 복사하지 않으려면 복사하는 함수를 작성하지 마십시오.
  • swap기능이 필요하지 않습니다 . (정렬 루틴을 작성하지 않는 한)
  • C ++ 0x 스타일 rvalue 참조는 필요하지 않습니다.
  • (N) RVO가 필요하지 않습니다.
  • 슬라이싱 문제가 없습니다.
  • 참조의 크기가 고정되어 있기 때문에 컴파일러가 객체 레이아웃을 결정하는 것이 더 쉽습니다.

의미 체계를 참조하는 주된 단점은 모든 개체에 잠재적으로 여러 참조가있을 때 개체를 언제 삭제해야하는지 알기가 어렵다는 것입니다. 당신은 거의 자동 메모리 관리를 할 수 있습니다.

Java는 비 결정적 가비지 수집기를 사용하기로 결정했습니다.

GC를 결정할 수 없는가?

예, 그럴 수 있습니다. 예를 들어 Python 의 C 구현 은 참조 횟수를 사용합니다. 그리고 나중에 참조 횟수가 실패하는 순환 가비지를 처리하기 위해 추적 GC를 추가했습니다.

그러나 재계 산은 엄청나게 비효율적입니다. 카운트를 업데이트하는 데 많은 CPU주기가 소비되었습니다. 업데이트가 동기화되어야하는 멀티 스레드 환경 (예 : Java와 같은 종류)에서 더 나쁩니다. 다른 것으로 전환해야 할 때까지 null 가비지 수집기 를 사용하는 것이 훨씬 좋습니다 .

Java는 파일 및 소켓과 같은 대체 불가능한 리소스를 희생하여 일반적인 사례 (메모리)를 최적화하기로 결정했다고 말할 수 있습니다. 오늘날 C ++에서 RAII를 채택한다는 관점에서 이것은 잘못된 선택처럼 보일 수 있습니다. 그러나 Java의 대상이되는 대부분은 이러한 것들을 명시 적으로 닫는 데 사용 된 C (또는 "클래스가있는 C") 프로그래머였습니다.

그러나 C ++ / CLI "스택 객체"는 어떻습니까?

그들은 단지 것 에 대한 문법 설탕Dispose ( 원래 링크를 많이 C 번호와 같은) using. 그러나 익명을 만들 수 gcnew FileStream("filename.ext")있고 C ++ / CLI가 자동 처리하지 않기 때문에 결정 론적 파괴의 일반적인 문제를 해결하지 못합니다 .


3
또한, 좋은 링크 (특히 첫 번째, 높은 이 토론 관련) .
BlueRaja-대니 Pflughoeft

using문은 많은 정리 관련 문제를 훌륭하게 처리하지만 다른 것들은 많이 남아 있습니다. 언어와 프레임 워크에 대한 올바른 접근 방식은 참조 IDisposable되지 않은 저장 위치와 "소유"된 저장 위치를 ​​선언적으로 구분 하는 것입니다. 참조를 소유 한 저장 위치를 ​​덮어 쓰거나 포기 IDisposable하면 반대 지시가없는 경우 대상을 삭제해야합니다.
supercat

1
"복사 생성자 필요 없음"은 훌륭하게 들리지만 실제로는 실패합니다. java.util.Date 및 Calendar가 가장 악명 높은 예일 수 있습니다. 보다 더 사랑스러운 사람은 없습니다 new Date(oldDate.getTime()).
케빈 클라인

2
iow RAII는 "폐기"되지 않았으며, 단순히 버려 질 존재는 없었습니다. (또는) 깊은 사본을 만들어 잊어 버려서는 안되는 사본간에 리소스를 공유하는 것을 잊었습니다.
jwenting

20

Java7는 C #과 비슷한 소개 using: 은 try-와-자원 정책을

try하나 이상의 자원을 선언 하는 진술. 자원 프로그램이 그것으로 종료 된 후에 종료되어야 목적으로한다. try가진 - - 자원 문은 각 자원이 문장의 끝에 닫혀 있는지 확인합니다. 구현 java.lang.AutoCloseable하는 모든 개체를 포함 하여 구현 하는 모든 개체 java.io.Closeable는 리소스로 사용할 수 있습니다.

그래서 그들은 의식적으로 RAII를 구현하지 않기로 선택하지 않았거나 그 사이에 마음을 바꿨습니다.


흥미롭지 만, 구현하는 객체에서만 작동하는 것 같습니다 java.lang.AutoCloseable. 아마 큰 문제는 아니지만 이것이 다소 제약을받는 느낌이 마음에 들지 않습니다. 어쩌면 나는 자동으로 관련되어야하는 다른 객체를 가지고 있지만 그것을 구현하게하는 것은 매우 의미 상 이상합니다 AutoCloseable.
FrustratedWithFormsDesigner

9
@ 패트릭 : 어, 그래서? usingRAII와 동일하지 않습니다. 어떤 경우에는 호출자가 리소스를 처리하는 것에 대해 걱정하고 다른 경우에는 수신자가 처리합니다.
BlueRaja-대니 Pflughoeft

1
+1 리소스 사용에 대해 몰랐습니다. 더 많은 상용구를 버리는 데 유용합니다.
jprete

3
using/ try-with-resources가 RAII와 동일하지 않은 경우 -1입니다 .
Sean McMillan

4
@Sean : 동의합니다. usingRAII 근처에는 어디에도 없습니다.
DeadMG

18

Java에는 의도적으로 스택 기반 객체 (일명 가치 객체)가 없습니다. 이것들은 메소드의 마지막에 객체가 자동으로 파괴되도록하는 데 필요합니다.

이것과 Java가 가비지 수집된다는 사실 때문에 결정 론적 마무리는 불가능합니다 (예를 들어, "로컬"객체가 다른 곳에서 참조 되었다면 어떻게 될까요?) ) .

그러나 네이티브 (C ++) 리소스와 상호 작용할 때를 제외하고 결정 론적 마무리 거의 필요 하지 않기 때문에 이것은 대부분의 사람들에게 좋습니다!


Java에 스택 기반 객체가없는 이유는 무엇입니까?

(프리미티브 이외 ..)

스택 기반 객체는 힙 기반 참조와 의미가 다릅니다. C ++에서 다음 코드를 상상해보십시오. 무엇을합니까?

return myObject;
  • 경우 myObject로컬 스택 기반 객체입니다 (결과가 무엇인가에 할당 된 경우) 복사 생성자가 호출된다.
  • 경우 myObject로컬 스택 기반 객체이며, 우리가 참조를 반환하고, 결과는 정의되지 않습니다.
  • 경우 myObject회원 / 전역 객체입니다 (결과가 무엇인가에 할당 된 경우) 복사 생성자가 호출된다.
  • 경우 myObject회원 / 전역 객체이며, 우리가 참조를 반환하고, 참조가 반환됩니다.
  • 경우 myObject로컬 스택 기반 객체에 대한 포인터이며, 그 결과는 정의되지 않는다.
  • 경우 myObject회원 / 전역 객체에 대한 포인터, 즉 포인터가 반환됩니다.
  • 경우 myObject힙 기반 객체에 대한 포인터, 즉 포인터가 반환됩니다.

이제 Java에서 동일한 코드는 무엇입니까?

return myObject;
  • 에 대한 참조 myObject가 반환됩니다. 변수가 로컬, 멤버 또는 전역 변수인지는 중요하지 않습니다. 스택 기반 객체 또는 포인터 사례가 없습니다.

위의 내용은 스택 기반 객체가 C ++에서 프로그래밍 오류의 일반적인 원인 인 이유를 보여줍니다 . 이 때문에 자바 디자이너들은 그것들을 꺼냈다. 그것들이 없으면 Java에서 RAII를 사용할 필요가 없습니다.


6
"RAII에는 아무런 의미가 없습니다"라는 말의 의미가 무엇인지 모르겠습니다. "Java에 RAII를 제공 할 수있는 능력이 없습니다"라는 의미입니다. RAII는 어떤 언어와도 독립적입니다 ... 그렇지 않습니다 특정 언어가 제공하지 않기 때문에 "무의미"
해지

4
그것은 정당한 이유가 아닙니다. 스택 기반 RAII를 사용하기 위해 객체가 실제로 스택에있을 필요는 없습니다. "고유 참조"와 같은 것이 있다면, 소멸자는 범위를 벗어나면 해고 될 수 있습니다. 예를 들어, D 프로그래밍 언어와의 작동 방식을 참조하십시오 : d-programming-language.org/exception-safe.html
Nemanja Trifunovic

3
@Nemanja : 객체가되지 않습니다 스택 기반 의미를 가지고 스택에 살고, 나는 그것을 않았다 않았다. 그러나 그것은 문제가 아닙니다. 내가 언급했듯이 문제는 스택 기반 의미 자체입니다.
BlueRaja-대니 Pflughoeft

4
@Aaronaught : 악마는 "거의 항상"그리고 "대부분"입니다. DB 연결을 닫지 않고 GC에 남겨두고 종료자를 트리거하면 단위 테스트에서 제대로 작동하고 프로덕션 환경에 배포 할 때 심각하게 중단됩니다. 언어에 관계없이 결정적인 정리가 중요합니다.
Nemanja Trifunovic

8
@NemanjaTrifunovic : 라이브 데이터베이스 연결에서 단위 테스트를하는 이유는 무엇입니까? 그것은 실제로 단위 테스트가 아닙니다. 아뇨, 죄송합니다. 사지 않습니다. 어쨌든 DB 연결을 생성해서는 안되며 생성자 또는 속성을 통해 전달해야하므로 스택과 같은 자동 파괴 의미를 원하지 않습니다 . 데이터베이스 연결에 의존하는 객체는 실제로 소유해야합니다. 비 결정적 정리가 자주 그렇게 힘들어하는 경우, 언어 디자인이 아니라 응용 프로그램 디자인이 잘못 되었기 때문입니다.
Aaronaught

17

구멍에 대한 설명 using이 불완전합니다. 다음 문제를 고려하십시오.

interface Bar {
    ...
}
class Foo : Bar, IDisposable {
    ...
}

Bar b = new Foo();

// Where's the Dispose?

제 생각에는 RAII와 GC가 모두 없다는 것은 나쁜 생각입니다. 이 자바 파일을 닫는에 올 때, 그것은이다 malloc()free()거기.


2
RAII가 꿀벌의 무릎이라는 것에 동의합니다. 그러나이 using절은 C # over Java에있어 큰 발전입니다. 결정 론적 파괴를 허용하고 따라서 자원 관리를 수정합니다 (RAII만큼 좋은 것은 아니지만 기억해 두어야합니다).
Martin York

8
"Java로 파일을 닫을 때는 malloc () 및 free ()입니다."– 물론입니다.
Konrad Rudolph

9
@ KonradRudolph : 그것은 malloc보다 나쁘고 무료입니다. 적어도 C에서는 예외가 없습니다.
Nemanja Trifunovic

1
@Nemanja : 당신은의 공정이 될 수하자 free()finally.
DeadMG

4
@Loki : 기본 클래스 문제는 문제로서 훨씬 더 중요합니다. 예를 들어, 원본 IEnumerable은에서 상속받지 않았으며 IDisposable결과로 구현할 수없는 많은 특수 반복자가있었습니다.
DeadMG

14

나는 꽤 늙었다. 나는 거기에 있었고 그것을 보았고 그것에 대해 여러 번 머리를 부딪쳤다.

저는 허 슬리 파크 (Hursley Park)에서 열린 컨퍼런스에서 IBM 소년들이이 새로운 Java 언어가 얼마나 멋진 지 말해주었습니다. 누군가 만 물었습니다. 그는 우리가 C ++에서 소멸자로 알고있는 것을 의미하지는 않았지만 finaliser없었습니다 (또는 finalisers가 있었지만 기본적으로 작동하지 않았습니다). 이것은 돌아 왔고, 우리는 Java가 그 시점에서 약간의 장난감 언어라고 결정했습니다.

이제 그들은 언어 사양에 Finalisers를 추가했으며 Java는 일부 채택을 보았습니다.

물론 나중에 모든 사람들은 GC를 엄청나게 늦추기 때문에 파이널 라이저를 객체에 넣지 말라고 들었습니다. (힙을 잠글뿐만 아니라 최종 대상이 될 객체를 임시 영역으로 이동해야했기 때문에 GC가 앱 실행을 일시 중지했기 때문에 이러한 메소드를 호출 할 수 없었습니다. 대신 다음 바로 전에 호출됩니다. GC 사이클) (그리고 더 나쁜 것은 앱이 종료 될 때 파이널 라이저가 전혀 호출되지 않는 경우가 있습니다. 파일 핸들을 닫지 않았다고 상상해보십시오)

그런 다음 우리는 C #을 가졌으며 MSDN에서이 새로운 C # 언어가 얼마나 멋진 지에 관한 토론 포럼을 기억합니다. 누군가 결정 론적 마무리가없는 이유를 물었고 MS 소년들은 우리에게 그러한 것들이 필요하지 않은 방법을 말한 다음 앱 디자인 방식을 변경해야한다고 말한 다음 GC가 얼마나 놀라운 지, 모든 오래된 앱이 어떻게되었는지 알려주었습니다. 모든 순환 참조 때문에 쓰레기와 결코 작동하지 않았습니다. 그런 다음 그들은 압력에 굴복하여 우리가 사용할 수있는 사양 에이 IDispose 패턴을 추가했다고 말했습니다. 그 시점에서 C # 앱에서 수동 메모리 관리로 되돌아 간 것으로 생각했습니다.

물론, MS 소년들은 나중에 그들이 우리에게 말한 모든 것이 ... 음, IDispose를 표준 인터페이스보다 조금 더 만들고 나중에 using 문을 추가했다는 것을 발견했습니다. W00t! 그들은 결정 론적 마무리가 결국 언어에서 빠졌다는 것을 깨달았습니다. 물론, 당신은 여전히 ​​어딘가에 넣는 것을 기억해야하므로 여전히 약간 수동이지만 더 좋습니다.

그렇다면 처음부터 각 스코프 블록에 사용 스타일 의미론을 자동으로 배치 할 수 있었을 때 왜 그렇게 했습니까? 아마 효율성이지만, 나는 그들이 깨닫지 못했다고 생각합니다. 결국 그들은 여전히 ​​.NET (Google SafeHandle)에 스마트 포인터가 필요하다는 것을 깨달았으므로 GC가 실제로 모든 문제를 해결할 것이라고 생각했습니다. 그들은 객체가 단순한 메모리 이상이고 GC가 주로 메모리 관리를 처리하도록 설계되었음을 잊었습니다. 그들은 GC가 이것을 처리 할 것이라는 생각에 사로 잡혔고, 당신이 거기에 다른 것들을 넣는 것을 잊어 버렸습니다. 오브젝트는 한동안 삭제하지 않으면 중요하지 않은 메모리 덩어리가 아닙니다.

그러나 원래 Java에 finalize 메소드가 없으면 조금 더 많은 것으로 생각합니다. 생성 한 객체는 메모리에 관한 것이고 DB 핸들이나 소켓 또는 기타와 같은 다른 것을 삭제하려는 경우 ) 그러면 수동으로해야 합니다.

Java는 사람들이 많은 수동 할당으로 C 코드를 작성하는 데 익숙한 임베디드 환경을 위해 설계되었으므로 자동 무료를 사용하지 않는 것은 큰 문제가 아니 었습니다. 이전에는 한 번도 해보지 않았으므로 Java에서 왜 필요할까요? 이 문제는 스레드 또는 스택 / 힙과 관련이 없으며 메모리 할당 (및 할당 해제)을 좀 더 쉽게하기 위해있을 수 있습니다. 대체로 try / finally 문은 비 메모리 리소스를 처리하기에 더 좋은 곳일 것입니다.

따라서 .NET이 Java의 가장 큰 결함을 단순히 복사 한 방식 인 IMHO는 가장 큰 약점입니다. .NET은 더 나은 Java가 아닌 더 나은 C ++이어야합니다.


IMHO의 경우 '블록 사용'과 같은 것이 결정 론적 정리를위한 올바른 접근 방법이지만 몇 가지 더 필요합니다. (2) 지시문으로 Dispose표시된 모든 필드 를 호출하기 위해 일상적인 메소드를 자동 생성하고 자동 호출 using여부 IDisposable.Dispose를 지정 하는 수단; (3)와 비슷 using하지만 Dispose예외가있는 경우 에만 호출되는 지시문 ; (4) 변형은 매개 변수를 IDisposable취할 것입니다 Exception...
supercat

... using적절한 경우 자동으로 사용됩니다 . 이 매개 변수는 블록이 정상적으로 종료 된 null경우 using또는 예외를 통해 종료 된 경우 보류중인 예외를 표시합니다. 그러한 것들이 존재한다면, 자원을 효과적으로 관리하고 누출을 피하는 것이 훨씬 쉬울 것입니다.
supercat

11

"Thinking in Java"및 "Thinking in C ++"의 저자이자 C ++ 표준위원회의 멤버 인 Bruce Eckel은 RAII뿐만 아니라 많은 영역에서 Gosling과 Java 팀 이 자신의 작업을 수행하지 않았다는 의견을 가지고 있습니다. 숙제.

... 언어가 불쾌하고 복잡하며 동시에 잘 설계 될 수있는 방법을 이해하려면 C ++의 모든 것이 멈추는 기본 디자인 결정을 명심해야합니다 .C. Stroustrup과의 호환성은 올바르게 결정했습니다. C 프로그래머가 대량의 객체로 이동하는 방법은 이동을 투명하게 만드는 것입니다. 이것은 큰 제약이었고 항상 C ++의 가장 큰 강점이었습니다 ... 그것이 C ++을 그랬던 것처럼 성공적이었고 복잡했던 것입니다.

또한 C ++을 충분히 이해하지 못하는 Java 디자이너를 속였습니다. 예를 들어, 프로그래머 과부하가 너무 어려워 프로그래머가 올바르게 사용할 수 없다고 생각했습니다. C ++에는 스택 할당과 힙 할당이 모두 있으며 모든 상황을 처리하고 메모리 누수를 일으키지 않도록 연산자를 오버로드해야하기 때문에 기본적으로 C ++에서는 마찬가지입니다. 실제로 어려움. 그러나 Java에는 단일 저장소 할당 메커니즘과 가비지 수집기가있어 C #에서 볼 수 있듯이 연산자 오버로드를 간단하게 만들 수 있습니다 (그러나 이미 Java를 포괄 한 Python에서는 이미 표시되었습니다). 그러나 수년간 Java 팀의 일부는 "운영자 과부하가 너무 복잡합니다."였습니다. 이것과 누군가가 분명히 한 다른 많은 결정들

다른 많은 예가 있습니다. 프리미티브는 "효율성을 위해 포함되어야했습니다." 정답은 "모든 것이 대상이다"라는 사실을 유지하고 효율성이 필요할 때 하위 수준의 활동을 수행 할 수있는 함정을 제공하는 것입니다 (이는 핫스팟 기술이 결국보다 효율적으로 일을 투명하게 할 수있게 해주었을 것입니다) 있다). 아, 그리고 부동 소수점 프로세서를 직접 사용하여 초월 함수를 계산할 수 없다는 사실은 소프트웨어에서 대신 수행됩니다. 나는 이와 같은 문제에 대하여 내가 설 수있는만큼 글을 썼으며, 내가 듣는 대답은 항상 "이것이 자바 방식"이라는 효과에 대한 팽팽한 답이었다.

제네릭이 어떻게 잘못 설계되었는지에 대해 글을 썼을 때, "우리는 Java에서 이루어진 이전의 (나쁜) 결정과 역 호환되어야합니다."와 같은 반응을 얻었습니다. 최근에 점점 더 많은 사람들이 Generics에 대해 충분한 경험을 쌓아서 실제로 사용하기가 매우 어렵다는 사실을 알았습니다. 실제로 C ++ 템플릿은 훨씬 강력하고 일관성이 있습니다 (컴파일러 오류 메시지가 허용되므로 훨씬 사용하기 쉬워졌습니다). 사람들은 도움이 되겠지만 스스로 부과 된 제약에 의해 무너지는 디자인에 흠집을 내지는 않을 것입니다.

목록은 지루한 지점까지 이어집니다 ...


5
이것은 RAII에 중점을두기보다는 Java 대 C ++ 답변처럼 들립니다. C ++과 Java는 각각 다른 언어이며 각각의 장단점이 있다고 생각합니다. 또한 C ++ 디자이너는 많은 영역에서 숙제를하지 않았습니다 (KISS 원칙이 적용되지 않음, 클래스 누락에 대한 간단한 가져 오기 메커니즘 등). 그러나 문제의 초점은 RAII였습니다. 이것은 Java에서 누락되었으며 수동으로 프로그래밍해야합니다.
Giorgio

4
@Giorgio :이 기사의 요점은 Java가 여러 가지 문제로 인해 보트를 놓친 것 같습니다. 그 중 일부는 RAII와 직접 관련이 있습니다. Cck와 Java에 미치는 영향과 관련하여 Eckels는 다음과 같이 지적합니다. "C ++의 모든 요소가 중단 된 주요 디자인 결정 : C와의 호환성을 명심해야합니다. 이는 큰 제약이었으며 항상 C ++의 가장 큰 강점이었습니다 ... 또한 C ++을 충분히 이해하지 못하는 Java 디자이너를 속였습니다. " C ++의 디자인은 Java에 직접 영향을 주었고 C #은 두 가지 모두를 배울 수있는 기회를 가졌습니다. (그렇지
않은가

2
@Giorgio 특정 패러다임과 언어 패밀리에서 기존 언어를 공부하는 것은 실제로 새로운 언어 개발에 필요한 과제의 일부입니다. 이것은 그들이 Java로 간단히 채찍질 한 예입니다. 그들은 볼 C ++과 스몰 토크를 가지고있었습니다. C ++에는 Java가 개발 될 때 살펴볼 Java가 없었습니다.
제레미

1
@Gnawme : "자바는 RAII와 직접 관련이있는 여러 가지 문제에서 보트를 놓친 것 같습니다."이러한 문제를 언급 할 수 있습니까? 게시 한 기사에 RAII가 언급되어 있지 않습니다.
Giorgio

2
@Giorgio Sure, C ++ 개발 이후 부족한 기능을 설명하는 혁신이있었습니다. C ++의 개발 이전에 확립 된 언어를 살펴 보아야 할 기능이 있습니까? 이것이 우리가 Java와 이야기하는 숙제의 종류입니다. Java 개발에서 모든 C ++ 기능을 고려하지 않을 이유는 없습니다. 다중 상속과 같은 일부는 의도적으로 제외했습니다. RAII와 같은 일부는 간과 한 것 같습니다.
Jeremy

10

가장 좋은 이유는 대부분의 답변보다 훨씬 간단합니다.

스택 할당 된 객체를 다른 스레드로 전달할 수 없습니다.

그만하고 생각 해봐 계속 생각하십시오 ... 이제 모든 사람들이 RAII에 관심이있을 때 C ++에는 스레드가 없었습니다. 너무 많은 객체를 전달하면 Erlang (스레드 당 별도의 힙)조차도 불안정합니다. C ++는 C ++ 2011에서만 메모리 모델을 얻었습니다. 이제 컴파일러의 "문서"를 참조하지 않고도 C ++의 동시성에 대해 거의 추론 할 수 있습니다.

Java는 (거의) 처음부터 여러 스레드를 위해 설계되었습니다.

Stroustrup이 스레드를 필요로하지 않는다고 보증하는 "C ++ 프로그래밍 언어"라는 구본을 여전히 가지고 있습니다.

두 번째 고통스러운 이유는 슬라이싱을 피하는 것입니다.


1
다중 스레드 용으로 설계된 Java도 GC가 참조 계산을 기반으로하지 않는 이유를 설명합니다.
dan04

4
@NemanjaTrifunovic : C ++ / CLI를 Java 또는 C #과 비교할 수 없으며 관리되지 않는 C / C ++ 코드와 상호 운용하기위한 목적으로 설계되었습니다. .NET 프레임 워크에 대한 액세스 권한을 부여하는 관리되지 않는 언어와 비슷합니다.
Aaronaught

3
@NemanjaTrifunovic : 그렇습니다. C ++ / CLI는 일반적인 응용 프로그램 에는 전혀 부적절한 방식으로 수행 할 수있는 방법의 한 예입니다 . 그건 단지 C / C ++ 상호 운용성에 유용합니다. 뿐만 아니라 일반 개발자는해야 하지 완전히 무관 "스택 또는 힙"결정에 안장 될 필요가 있지만, 만약 당신이 그것을 리팩토링하려고하면 그것은 실수로 널 포인터 / 참조 오류 및 / 또는 메모리 누수를 만들 하찮게 쉽다. 내가 생각하지 않기 때문에 죄송하지만, 당신이 이제까지 실제로 자바 나 C #에서 프로그래밍 한 경우 궁금해 할 사람이 실제로 것이다있다 원하는 C ++ / CLI에 사용되는 의미를.
Aaronaught

2
@Aaronaught : Java (약간)와 C # (많은)으로 프로그래밍했으며 현재 프로젝트는 거의 모든 C #입니다. 나를 믿어, 내가 무슨 말을하는지 알고, 그것은 "스택 대 힙"과 아무 관련이 없다-그것은 당신이 필요로하지 않는 한 빨리 모든 자원이 해제되도록하는 것과 관련이있다. 자동으로. 그렇지 않은 경우 - 당신은 곤경에 얻을.
Nemanja Trifunovic

4
@ NemanjaTrifunovic : 훌륭합니다.하지만 C #과 C ++ / CLI는이를 원할 때 명시 적으로 명시해야하며 다른 구문을 사용합니다. 현재 당신이 겪고있는 필수 요점을 논박하는 사람은 없습니다 ( "필요하지 않은 즉시 리소스가 해제 됨"). 콜 스택 기반의 결정적 폐기- " 그것은 단지 물을 보유하지 않습니다.
Aaronaught

5

C ++에서는보다 범용적인 저급 언어 기능 (스택 기반 객체에서 자동으로 소멸자 호출)을 사용하여 상위 수준 (RAII)을 구현합니다.이 접근 방식은 C # / Java 직원이 아닌 것으로 보입니다 너무 좋아. 오히려 특정 요구에 맞는 특정 고급 도구를 디자인하고 언어에 내장 된 기성품 프로그래머에게 제공합니다. 이러한 특정 도구의 문제점은 종종 사용자 정의가 불가능하다는 것입니다 (일부 배우기가 매우 쉽습니다). 더 작은 블록으로 빌드 할 때 더 나은 솔루션이 시간이지나면서 나올 수 있지만 , 기본 제공되는 고급 구조 있는 경우 에는 그 가능성이 줄어 듭니다.

그래서, 나는 (실제로 거기에 없었습니다 ...) 언어를 더 쉽게 고를 수있게하는 것을 목표로 한 결정적인 결정이라고 생각하지만, 제 생각에는 나쁜 결정이었습니다. 다시 한 번, 저는 일반적으로 C ++ 프로그래머에게 기회를 부여하고 자신의 철학을 선호하므로 약간 편견이 있습니다.


7
"프로그래머에게 기회를주기 위해 자신의 철학을 부여하라"는 고유 한 문자열 클래스와 스마트 포인터를 각각 롤링 한 프로그래머가 작성한 라이브러리를 결합해야 할 때까지 훌륭하게 작동합니다.
dan04

@ dan04를 통해 사전 정의 된 문자열 클래스를 제공하는 관리되는 언어를 사용하여 원숭이 패치를 할 수 있습니다. 이는 다른 자체 롤링 문자열에 대처할 수없는 사람이라면 재난을위한 레시피입니다. 수업.
gbjbaanb

-1

C #에서 Dispose메소드 와 함께 이것과 대략 동등한 것을 이미 호출했습니다 . 자바도있다 finalize. 참고 : Java의 finalize는 결정적이지 않으며와 다릅니다. Dispose둘 다 GC와 함께 리소스를 청소하는 방법이 있음을 지적합니다.

객체가 물리적으로 파괴되어야하기 때문에 C ++이 더 고통 스럽다면. C # 및 Java와 같은 고급 언어에서는 더 이상 참조가없는 경우이를 정리하기 위해 가비지 수집기에 의존합니다. C ++의 DBConnection 객체에 불량 참조 나 포인터가 없다는 보장은 없습니다.

그렇습니다. C ++ 코드는보다 직관적으로 읽을 수 있지만 Java와 같은 언어가 적용하는 경계와 제한으로 인해 더욱 까다 롭고 어려운 버그가 배제되고 일반적인 루키 실수로부터 다른 개발자를 보호하기 때문에 악몽이 될 수 있습니다.

아마도 저와 같은 C ++의 저수준의 힘, 제어 및 순도와 같은 환경 설정으로 귀결 될 수 있습니다.


12
우선 Java의 "finalize"는 결정적 이지 않습니다 . C #의 "dispose"또는 C ++의 소멸자와 동일 하지 않습니다 . 또한 .NET을 사용하는 경우 C ++에도 가비지 수집기가 있습니다.
JoelFan

2
@DeadMG : 문제는 바보가 아닐 수도 있지만 회사를 방금 떠난 (그리고 현재 유지 관리하는 코드를 작성한) 다른 사람 일 수도 있습니다.
케빈

7
그 녀석은 당신이 뭐든지간에 칙칙한 코드를 작성하려고합니다. 당신은 나쁜 프로그래머를 데리고 좋은 코드를 작성하게 만들 수 없습니다. 바보 포인터는 바보를 다룰 때 가장 걱정되는 부분입니다. 우수한 코딩 표준은 추적해야하는 메모리에 스마트 포인터를 사용하므로 스마트 관리는 메모리를 안전하게 할당 해제하고 액세스하는 방법을 분명하게해야합니다.
DeadMG

3
DeadMG가 말한 것. C ++에는 나쁜 점이 많이 있습니다. 그러나 RAII는 오랜 기간 동안 그중 하나가 아닙니다. 실제로, 자원 관리를 올바르게 설명 할 Java 및 .NET의 부족 (메모리가 유일한 자원이기 때문에?)이 가장 큰 문제 중 하나입니다.
Konrad Rudolph

8
제 생각에는 최종 디자인은 현명한 재난 디자인입니다. 메모리 관리 측면이 아니라 리소스 관리 측면에서 디자이너에서 개체 사용자에게 개체를 올바르게 사용하도록 강요합니다. C ++에서 리소스 관리를 올바르게 수행하는 것은 클래스 디자이너의 책임입니다 (한 번만 수행). Java에서 클래스 관리는 자원 관리를 올바르게 수행해야하므로 클래스를 사용할 때마다 수행해야합니다. stackoverflow.com/questions/161177/…
Martin York
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.