용어 및 개념의 의미 이해-RAII (Resource Acquisition is Initialization)


110

C ++ 개발자가 RAII가 무엇인지, 왜 중요한지, 다른 언어와 관련이 있는지 여부에 대해 설명해 주시겠습니까?

나는 이렇게 조금 알고있다. 나는 그것이 "자원 획득이 초기화이다"를 의미한다고 믿는다. 그러나 그 이름은 RAII가 무엇인지에 대한 나의 (아마도 부정확 한) 이해와 동 떨어지지 않습니다. RAII가 스택에서 객체를 초기화하는 방법이라는 인상을 받았기 때문에 해당 변수가 범위를 벗어나면 소멸자가 자동으로 리소스가 정리되도록 호출됩니다.

그렇다면 "스택을 사용하여 정리를 트리거"(UTSTTC :)하지 않는 이유는 무엇입니까? 거기에서 "RAII"까지 어떻게 가나 요?

그리고 힙에있는 무언가를 정리하도록 스택에 무언가를 어떻게 만들 수 있습니까? 또한 RAII를 사용할 수없는 경우가 있습니까? 가비지 수집을 원하십니까? 적어도 일부 개체에 대해 사용할 수있는 가비지 수집기는 다른 개체를 관리하도록 허용합니까?

감사.


27
UTSTTC? 나는 그것을 좋아한다! RAII보다 훨씬 직관적입니다. RAII의 이름 잘못 되었기 때문에 C ++ 프로그래머가 이에 대해 이의를 제기 할 것 같지 않습니다. 그러나 변경하기는 쉽지 않습니다. ;)
jalf

10
문제에 대한 Stroustrup의 견해는 다음과 같습니다. groups.google.com/group/comp.lang.c++.moderated/msg/…
sbi

3
@sbi : 어쨌든, 역사 연구를 위해 귀하의 의견에 +1하십시오. 나는 개념의 이름 (RAII)에 대한 저자 (B. Stroustrup)의 관점을 갖는 것이 그 자체의 대답을 가질만큼 충분히 흥미 롭다고 생각합니다.
paercebal

1
@paercebal : 역사 연구? 이제 당신은 나를 아주 늙었다 고 느끼게 만들었습니다. :(나는 그 당시 전체 스레드를 읽고 있었으며 자신을 C ++ 초보자라고 생각조차하지 않았습니다!
sbi

3
+1, 저도 같은 질문을하려고했는데, 저만이 개념을 이해하는 것은 아니지만 이름을 이해하지 못하는 것 같아 기쁩니다. RAOI-Resource Acquisition On Initialization이라고 불렸어야하는 것 같습니다.
laurent 2011

답변:


132

그렇다면 "스택을 사용하여 정리를 트리거"(UTSTTC :)하지 않는 이유는 무엇입니까?

RAII는해야 할 일을 알려줍니다. 생성자에서 리소스를 획득하십시오! 하나의 리소스, 하나의 생성자를 추가합니다. UTSTTC는 그 중 하나 일 뿐이며 RAII는 그 이상입니다.

자원 관리가 엉망입니다. 여기서 리소스는 사용 후 정리가 필요한 모든 것입니다. 많은 플랫폼에 걸친 프로젝트 연구에 따르면 대부분의 버그는 리소스 관리와 관련이 있으며 특히 Windows에서는 좋지 않습니다 (많은 유형의 개체 및 할당 자 때문에).

C ++에서 리소스 관리는 예외와 (C ++ 스타일) 템플릿의 조합으로 인해 특히 복잡합니다. 후드를 들여다 보려면 GOTW8을 참조하십시오 .)


C ++ 는 생성자가 성공한 경우에만 소멸자가 호출되도록 보장합니다 . 이를 바탕으로 RAII는 일반 프로그래머가 인식하지 못하는 많은 문제를 해결할 수 있습니다. 다음은 "내가 돌아올 때마다 내 지역 변수가 소멸 될 것"이외의 몇 가지 예입니다.

FileHandleRAII를 사용 하는 지나치게 단순한 클래스 부터 시작하겠습니다 .

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

구성이 실패하면 (예외 있음) 소멸자조차도 다른 멤버 함수가 호출되지 않습니다.

RAII는 잘못된 상태의 개체 사용을 방지합니다. 우리가 물건을 사용하기도 전에 이미 삶을 더 쉽게 만들어줍니다.

이제 임시 객체를 살펴 보겠습니다.

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

처리해야 할 세 가지 오류 사례가 있습니다. 파일을 열 수없고, 하나의 파일 만 열 수 있으며, 두 파일을 모두 열 수 있지만 파일 복사는 실패했습니다. 비 RAII 구현에서는 Foo세 가지 경우를 모두 명시 적으로 처리해야합니다.

RAII는 하나의 문 내에서 여러 리소스를 획득 한 경우에도 획득 한 리소스를 해제합니다.

이제 몇 가지 개체를 집계 해 보겠습니다.

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

의 생성자는 Logger경우 실패 할 것 original'(때문에 생성자가 실패 filename1열 수 없습니다), duplex(때문에 생성자 실패'를 filename2열 수 없습니다), 또는 내부의 파일에 쓰기 Logger의 생성자 몸이 실패합니다. 이러한 경우의에서 Logger의 소멸자는 것입니다 하지 호출 - 우리가 의존 할 수 있도록 Logger'파일을 해제 소멸자를. 그러나 생성 된 경우 original생성자를 정리하는 동안 소멸자가 호출됩니다 Logger.

RAII는 부분 시공 후 정리를 단순화합니다.


부정적인 점 :

부정적인 점? 모든 문제는 RAII 및 스마트 포인터로 해결할 수 있습니다 ;-)

RAII는 지연된 획득이 필요할 때 때때로 다루기 어렵고 집계 된 객체를 힙으로 푸시합니다.
Logger에 SetTargetFile(const char* target). 이 경우 여전히의 멤버 여야하는 핸들 Logger은 힙에 있어야합니다 (예 : 핸들의 소멸을 적절하게 트리거하기 위해 스마트 포인터에 있음).

나는 정말로 가비지 수집을 바라지 않았습니다. C #을 할 때 가끔 신경 쓸 필요가없는 행복의 순간을 느끼지만, 결정 론적 파괴를 통해 만들어 질 수있는 모든 멋진 장난감이 훨씬 더 그립습니다. (사용하는 IDisposable것만으로는 자르지 않습니다.)

나는 GC의 혜택을받을 수있는 특히 복잡한 구조를 가지고 있는데, "단순한"스마트 포인터는 여러 클래스에 대한 순환 참조를 유발합니다. 우리는 강점과 약점의 균형을 신중하게 조정하면서 뒤죽박죽했지만 무언가를 바꾸고 싶을 때마다 큰 관계 차트를 연구해야합니다. GC가 더 좋았을 수도 있지만 일부 구성 요소에는 최대한 빨리 릴리스해야하는 리소스가 있습니다.


FileHandle 샘플에 대한 참고 사항 : 완전한 것이 아니라 샘플 일 뿐이며 잘못된 것으로 판명되었습니다. 지적 해 주신 Johannes Schaub와 올바른 C ++ 0x 솔루션으로 전환 한 FredOverflow에게 감사드립니다. 시간이 지남에 따라 여기에 문서화 된 접근 방식을 채택했습니다 .


1
+1 GC와 ASAP가 맞물리지 않음을 가리 킵니다. 자주 다치게하지하지만 그렇게되면 그것은 진단하기 쉽지 않다 않습니다 : /
마티유 M.

10
특히 내가 이전에 간과했던 한 문장. "RAII"가 "생성자 내부에서 리소스 획득"이라고 말합니다. 그것은 말이되며 "RAII"의 거의 한마디로 의역합니다. 이제 더 나아졌습니다 (할 수 있다면 다시 투표하겠습니다 :)
Charlie Flowers

2
GC의 주요 이점 중 하나는 메모리 할당 프레임 워크가 "안전하지 않은"코드가 없을 때 댕글 링 참조 생성을 방지 할 수 있다는 것입니다 ( "안전하지 않은"코드가 허용되는 경우 프레임 워크는 아무것도 방지 할 수 없습니다). GC는 또한 종종 명확한 소유자가없고 정리가 필요하지 않은 문자열과 같은 공유 불변 객체를 처리 할 때 RAII보다 우수합니다 . 대부분의 응용 프로그램에는 변경 불가능한 객체 (GC가 가장 좋은 곳)와 정리가 필요한 객체 (RAII가 가장 좋은 곳)가 혼합되어 있기 때문에 더 많은 프레임 워크가 GC와 RAII를 결합하려고하지 않는 것은 유감입니다.
supercat

@supercat : 저는 일반적으로 GC를 좋아하지만 GC가 "이해하는"자원에 대해서만 작동합니다. 예를 들어 .NET GC는 COM 개체의 비용을 알지 못합니다. 단순히 루프에서 생성하고 파괴 할 때 GC를 수행 할 생각조차하지 않고도 애플리케이션이 주소 공간 또는 가상 메모리와 관련하여 실행될 수 있습니다. --- 게다가 완벽하게 GC 환경에서도 결정 론적 파괴의 힘을 놓치고 있습니다. 예를 들어 인증 조건에서 UI 요소를 표시하는 등 다른 인공물에 동일한 패턴을 적용 할 수 있습니다.
peterchen

@peterchen : OOP와 관련된 많은 생각에없는 한 가지는 객체 소유권의 개념입니다. 소유권을 추적하는 것은 종종 리소스가있는 개체에 대해 분명히 필요하지만 리소스가없는 변경 가능한 개체에도 종종 필요합니다. 일반적으로 객체는 공유 가능한 불변 객체에 대한 참조 또는 배타적 소유자 인 변경 가능한 객체에서 변경 가능한 상태를 캡슐화해야합니다. 이러한 배타적 소유권이 반드시 배타적 쓰기 액세스를 의미하는 것은 아니지만을 Foo소유 Bar하고 Boz변경하는 경우 ...
supercat

42

거기에 훌륭한 답변이 있으므로 잊혀진 것들을 추가합니다.

0. RAII는 범위에 관한 것입니다.

RAII는 다음 두 가지에 관한 것입니다.

  1. 생성자에서 리소스 (어느 리소스에 관계없이)를 획득하고 소멸자에서 획득을 해제합니다.
  2. 변수가 선언되면 생성자가 실행되고 변수가 범위를 벗어나면 소멸자가 자동으로 실행됩니다.

다른 사람들은 이미 그것에 대해 대답 했으므로 자세히 설명하지 않겠습니다.

1. Java 또는 C #으로 코딩 할 때 이미 RAII를 사용하고 있습니다.

MONSIEUR JOURDAIN : 뭐! 내가 "니콜, 내 슬리퍼 가져와 내 나이트캡 줘"라고 말하면 그게 산문이야?

철학 석사 : 네, 선생님.

MONSIEUR JOURDAIN : 40 년이 넘도록 나는 그것에 대해 아무것도 알지 못한 채 산문을 말하고 있으며, 그것을 가르쳐 준 것에 대해 당신에게 많은 의무가 있습니다.

— 몰리에르 : 중산층 신사, 2 막, 4 장

Monsieur Jourdain이 산문에서했던 것처럼 C #과 심지어 Java 사람들은 이미 RAII를 사용하지만 숨겨진 방식으로 사용합니다. 예를 들어 다음 Java 코드 (를로 대체 synchronized하여 C #에서 동일한 방식으로 작성 됨 lock) :

void foo()
{
   // etc.

   synchronized(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

... 이미 RAII를 사용하고 있습니다 : 뮤텍스 획득은 키워드 ( synchronized또는lock )에서 수행되며 스코프를 종료 할 때 획득 해제가 수행됩니다.

표기법이 너무 자연 스럽기 때문에 RAII에 대해 들어 본 적이없는 사람들에게도 설명이 거의 필요하지 않습니다.

여기서 C ++가 Java 및 C #에 비해 갖는 장점은 RAII를 사용하여 무엇이든 만들 수 있다는 것입니다. 예를 들어, synchronized또는lock ++ C에서, 그러나 우리는 여전히있을 수 있습니다.

C ++에서는 다음과 같이 작성됩니다.

void foo()
{
   // etc.

   {
      Lock lock(someObject) ; // lock is an object of type Lock whose
                              // constructor acquires a mutex on
                              // someObject and whose destructor will
                              // un-acquire it 

      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

Java / C # 방식으로 쉽게 작성할 수 있습니다 (C ++ 매크로 사용).

void foo()
{
   // etc.

   LOCK(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

2. RAII는 다른 용도로 사용됩니다.

WHITE RABBIT : [노래] 늦었 어 / 늦었 어 / 아주 중요한 데이트를 위해. / "안녕하세요"라고 말할 시간이 없습니다. / 안녕. / 늦었 어 늦었 어 늦었 어

— 이상한 나라의 앨리스 (디즈니 버전, 1951)

생성자가 언제 호출 될 것인지 (객체 선언에서), 해당 소멸자가 언제 호출 될 것인지 (스코프 종료시) 알고 있으므로 한 줄만 사용하여 거의 마법 같은 코드를 작성할 수 있습니다. C ++ 원더 랜드에 오신 것을 환영합니다 (적어도 C ++ 개발자의 관점에서는).

예를 들어, 위의 잠금 객체가 사용 된 것처럼 카운터 객체를 작성하고 (실습으로 사용하겠습니다) 변수를 선언하여 사용할 수 있습니다.

void foo()
{
   double timeElapsed = 0 ;

   {
      Counter counter(timeElapsed) ;
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

물론 매크로를 사용하여 Java / C # 방식으로 다시 작성할 수 있습니다.

void foo()
{
   double timeElapsed = 0 ;

   COUNTER(timeElapsed)
   {
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

3. 왜 C ++가 부족 finally합니까?

[SHOUTING] 마지막 카운트 다운입니다!

— 유럽 : The Final Countdown (죄송합니다. 여기에 인용문이 없습니다. :-)

finally절은 C # / Java에서 범위 종료시 ( return또는 throw 된 예외를 통해) 리소스 폐기를 처리하는 데 사용됩니다 .

예리한 사양 독자라면 C ++에 finally 절이 없음을 알게 될 것입니다. 그리고 이것은 RAII가 이미 자원 폐기를 처리하기 때문에 C ++에서 필요하지 않기 때문에 오류가 아닙니다. (그리고 C ++ 소멸자를 작성하는 것이 올바른 Java finally 절이나 C #의 올바른 Dispose 메서드를 작성하는 것보다 훨씬 쉽습니다).

그래도 때로는 finally절이 멋질 것입니다. C ++로 할 수 있습니까? 응 우리는 할 수있어! 그리고 RAII를 번갈아 사용합니다.

결론 : RAII는 C ++의 철학 그 이상입니다. C ++입니다.

RAII? 이것은 C ++입니다 !!!

— 모호한 스파르타 왕과 그의 300 명의 친구들이 뻔뻔하게 복사 한 C ++ 개발자의 분노한 댓글

당신이 C에서의 경험을 어떤 수준에 도달하면 ++, 당신의 측면에서 생각을 시작 RAII 의 관점에서, 실행을 자동화 construtors와 소멸자 .

당신의 관점에서 생각하기 시작 범위{} 문자가 코드에서 가장 중요한 요소 중 하나가됩니다.

그리고 거의 모든 것이 RAII 측면에서 적합합니다 : 예외 안전, 뮤텍스, 데이터베이스 연결, 데이터베이스 요청, 서버 연결, 시계, OS 핸들 등 그리고 마지막으로 중요한 것은 메모리입니다.

데이터베이스 부분은 무시할 수 없습니다. 가격을 지불하면 " 트랜잭션 프로그래밍 "스타일로 작성하여 최종적으로 모든 변경 사항을 커밋 할 것인지 결정할 때까지 코드 줄과 줄을 실행할 수 있습니다. , 또는 가능하지 않은 경우 모든 변경 사항을 되 돌리십시오 (각 줄이 최소한 강력한 예외 보장을 충족하는 한). ( 트랜잭션 프로그래밍에 대해서는 이 Herb 's Sutter 기사의 두 번째 부분을 참조하십시오 ).

그리고 퍼즐처럼 모든 것이 맞습니다.

RAII는 C ++의 많은 부분이므로 C ++가 없으면 C ++가 될 수 없습니다.

이것은 경험이 풍부한 C ++ 개발자가 RAII에 매료 된 이유와 RAII가 다른 언어를 시도 할 때 가장 먼저 검색하는 이유를 설명합니다.

그리고 Garbage Collector는 그 자체로 훌륭한 기술이지만 C ++ 개발자의 관점에서 그다지 인상적이지 않은 이유를 설명합니다.

  • RAII는 이미 GC가 처리하는 대부분의 케이스를 처리합니다.
  • GC는 순수 관리 개체에 대한 순환 참조를 사용하여 RAII보다 더 잘 처리합니다 (약한 포인터의 현명한 사용으로 완화됨).
  • 여전히 GC는 메모리로 제한되지만 RAII는 모든 종류의 리소스를 처리 할 수 ​​있습니다.
  • 위에서 설명한 것처럼 RAII는 훨씬 더 많은 일을 할 수 있습니다.

자바 팬 : GC는 모든 메모리를 처리하고 많은 잠재적 버그에서 벗어나기 때문에 RAII보다 훨씬 더 유용하다고 말하고 싶습니다. GC를 사용하면 순환 참조를 생성하고 참조를 반환 및 저장할 수 있으며 잘못 이해하기 어렵습니다 (단명 한 객체에 대한 참조를 저장하면 일종의 메모리 누수 인 라이브 시간이 길어 지지만 그게 유일한 문제입니다). . GC로 리소스를 처리하는 것은 작동하지 않지만 응용 프로그램의 대부분의 리소스는 사소한 라이브주기를 가지며 나머지 몇 개는 큰 문제가 아닙니다. 나는 우리가 GC와 RAII를 둘 다 가질 수 있기를 바랍니다. 그러나 그것은 불가능 해 보입니다.
maaartinus

16

1
그들 중 일부는 내 질문과 일치하지만 검색으로도 나타나지 않았고 새 질문을 입력 한 후에 나타나는 "관련 질문"목록도 나타나지 않았습니다. 링크 주셔서 감사합니다.
Charlie Flowers

1
@Charlie : 검색 빌드는 어떤면에서 매우 약합니다. 태그 구문 ( "[topic]")을 사용하는 것은 매우 유용하며 많은 사람들이 google을 사용합니다.
dmckee --- ex-moderator kitten

10

RAII는 C ++ 소멸자 의미 체계를 사용하여 리소스를 관리합니다. 예를 들어 스마트 포인터를 고려하십시오. 객체의 주소로이 포인터를 초기화하는 포인터의 매개 변수화 된 생성자가 있습니다. 스택에 포인터를 할당합니다.

SmartPointer pointer( new ObjectClass() );

스마트 포인터가 범위를 벗어나면 포인터 클래스의 소멸자가 연결된 개체를 삭제합니다. 포인터는 스택 할당되고 객체는 힙 할당됩니다.

RAII가 도움이되지 않는 특정한 경우가 있습니다. 예를 들어, 참조 계산 스마트 포인터 (예 : boost :: shared_ptr)를 사용하고주기가있는 그래프와 같은 구조를 만드는 경우주기의 개체가 서로 해제되는 것을 방지하므로 메모리 누수가 발생할 위험이 있습니다. 가비지 수집은 이에 대해 도움이 될 것입니다.


2
그래서 :) UCDSTMR 호출 할 필요가
다니엘 Daranas

다시 생각하면 UDSTMR이 더 적절하다고 생각합니다. 언어 (C ++)가 제공되므로 두문자어에 문자 "C"가 필요하지 않습니다. UDSTMR은 Use Destructor Semantics To Manage Resources를 의미합니다.
다니엘 Daranas

9

이전 답변보다 조금 더 강하게 말하고 싶습니다.

RAII, Resource Acquisition Is Initialization 은 획득 한 모든 자원이 객체 초기화의 맥락에서 획득되어야 함을 의미합니다. 이것은 "네이 키드"자원 획득을 금지합니다. 이론적 근거는 C ++의 정리가 함수 호출 기반이 아닌 객체 기반으로 작동한다는 것입니다. 따라서 모든 정리는 함수 호출이 아닌 개체에 의해 수행되어야합니다. 이런 의미에서 C ++는 Java보다 객체 지향적입니다. Java 정리는 finally절의 함수 호출을 기반으로합니다 .


좋은 대답입니다. 그리고 "객체의 초기화"는 "생성자"를 의미합니다. 그렇죠?
Charlie Flowers

@Charlie : 예, 특히이 경우에 그렇습니다.
MSalters

8

나는 cpitis에 동의합니다. 그러나 리소스는 메모리뿐만 아니라 어떤 것도 될 수 있다고 덧붙이고 싶습니다. 리소스는 파일, 중요 섹션, 스레드 또는 데이터베이스 연결 일 수 있습니다.

자원을 제어하는 ​​객체가 생성 될 때 자원을 획득하기 때문에 Resource Acquisition Is Initialization이라고하며, 생성자가 실패하면 (예 : 예외로 인해) 자원을 획득하지 못합니다. 그런 다음 개체가 범위를 벗어나면 리소스가 해제됩니다. C ++는 성공적으로 생성 된 스택의 모든 객체가 소멸되도록 보장합니다 (여기에는 수퍼 클래스 생성자가 실패하더라도 기본 클래스 및 멤버의 생성자가 포함됩니다).

RAII의 합리적인 이유는 리소스 획득 예외를 안전하게 만드는 것입니다. 획득 한 모든 리소스는 예외가 발생하더라도 적절하게 해제됩니다. 그러나 이것은 리소스를 획득하는 클래스의 품질에 의존합니다 (이것은 예외적으로 안전해야하며 어렵습니다).


훌륭합니다. 이름의 근거를 설명 해주셔서 감사합니다. 내가 이해 한대로 RAII를 "(생성자 기반) 초기화 이외의 다른 메커니즘을 통해 자원을 획득하지 마십시오"라고 RAII를 의역 할 수 있습니다. 예?
Charlie Flowers

예, 이것은 내 정책이지만 예외에 안전해야하므로 내 자신의 RAII 클래스를 작성하는 것을 매우 조심합니다. 내가 작성할 때 전문가가 작성한 다른 RAII 클래스를 재사용하여 예외 안전을 보장하려고합니다.
iain 2009

글쓰기가 어렵지 않았습니다. 수업이 충분히 작 으면 전혀 어렵지 않습니다.
Rob K

7

가비지 수집의 문제는 RAII에 중요한 결정 론적 파괴를 잃는다는 것입니다. 변수가 범위를 벗어나면 개체가 회수되는시기는 가비지 수집기에 달려 있습니다. 객체가 보유한 리소스는 소멸자가 호출 될 때까지 계속 보유됩니다.


4
문제는 결정론 만이 아닙니다. 진짜 문제는 종료 자 (자바 이름 지정)가 GC를 방해한다는 것입니다. GC는 죽은 물체를 기억하지 않고 무시하기 때문에 효율적입니다. GC는 객체가 호출
되었는지 확인

1
java / c #을 제외하고는 finalizer가 아닌 finally 블록에서 정리할 수 있습니다.
jk.

4

RAII는 Resource Allocation Is Initialization에서 제공됩니다. 기본적으로 생성자가 실행을 완료하면 생성 된 객체가 완전히 초기화되고 사용할 준비가 된 것입니다. 또한 소멸자가 객체가 소유 한 모든 리소스 (예 : 메모리, OS 리소스)를 해제 할 것임을 의미합니다.

가비지 수집 된 언어 / 기술 (예 : Java, .NET)과 비교할 때 C ++는 개체의 수명을 완전히 제어 할 수 있습니다. 스택 할당 객체의 경우 객체의 소멸자가 호출 될 때 (실행이 범위를 벗어날 때), 가비지 콜렉션의 경우 실제로 제어되지 않는 것을 알 수 있습니다. C ++에서 스마트 포인터 (예 : boost :: shared_ptr)를 사용하더라도 뾰족한 개체에 대한 참조가 없을 때 해당 개체의 소멸자가 호출된다는 것을 알 수 있습니다.


3

그리고 힙에있는 무언가를 정리하도록 스택에 무언가를 어떻게 만들 수 있습니까?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

int_buffer 인스턴스가 존재하게되면 크기가 있어야하며 필요한 메모리를 할당합니다. 범위를 벗어나면 소멸자가 호출됩니다. 이것은 동기화 개체와 같은 것에 매우 유용합니다. 치다

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

또한 RAII를 사용할 수없는 경우가 있습니까?

아니 정말.

가비지 수집을 원하십니까? 적어도 일부 개체에 대해 사용할 수있는 가비지 수집기는 다른 개체를 관리하도록 허용합니까?

못. 가비지 수집은 동적 리소스 관리의 극히 일부만 해결합니다.


저는 Java와 C #을 거의 사용하지 않았기 때문에 놓칠 일이 없었습니다.하지만 GC를 사용해야 할 때 리소스 관리에 관해서는 RAII를 사용할 수 없었기 때문에 확실히 제 스타일이 좁았습니다.
Rob K

1
저는 C #을 많이 사용했으며 100 % 동의합니다. 사실, 비 결정적 GC는 언어의 책임이라고 생각합니다.
Nemanja Trifunovic

2

여기에는 이미 많은 좋은 답변이 있지만 추가하고 싶습니다.
RAII에 대한 간단한 설명은 C ++에서 스택에 할당 된 객체가 범위를 벗어날 때마다 파괴된다는 것입니다. 즉, 개체 소멸자가 호출되고 필요한 모든 정리를 수행 할 수 있습니다.
즉, "새로 만들기"없이 개체를 만들면 "삭제"가 필요하지 않습니다. 그리고 이것은 또한 "스마트 포인터"뒤에있는 아이디어입니다. 스택에 상주하며 본질적으로 힙 기반 개체를 래핑합니다.


1
아니요, 그렇지 않습니다. 하지만 힙에 스마트 포인터를 만들어야하는 타당한 이유가 있습니까? 그런데 스마트 포인터는 RAII가 유용 할 수있는 예일뿐입니다.
E Dominique

1
아마도 "스택"대 "힙"을 사용하는 것이 약간 엉성한 것일 수 있습니다. "스택"에있는 개체는 로컬 개체를 의미합니다. 이는 자연스럽게 객체의 일부가 될 수 있습니다 (예 : 힙). "힙에 스마트 포인터 만들기"는 스마트 포인터 자체에 새로 만들기 / 삭제를 사용하는 것을 의미했습니다.
E Dominique

1

RAII는 Resource Acquisition Is Initialization의 약어입니다.

이 기술은 생성자 및 소멸자 및 거의 자동으로 전달되는 인수와 일치하는 생성자 또는 최악의 경우 기본 생성자가 호출되고 명시 적으로 제공된 경우 소멸자가 호출되고 그렇지 않으면 기본 생성자에 대한 지원으로 인해 C ++에서 매우 고유합니다. C ++ 컴파일러에 의해 추가 된 것은 C ++ 클래스에 대해 소멸자를 명시 적으로 작성하지 않은 경우 호출됩니다. 이것은 자동 관리되는 C ++ 객체에 대해서만 발생합니다. 즉, 무료 저장소를 사용하지 않습니다 (new, new [] / delete, delete [] C ++ 연산자를 사용하여 메모리가 할당 / 해제 됨).

RAII 기술은이 자동 관리 객체 기능을 사용하여 new / new []를 사용하여 추가 메모리를 명시 적으로 요청하여 힙 / 프리 스토어에 생성 된 객체를 처리합니다. 이는 delete / delete []를 호출하여 명시 적으로 삭제해야합니다. . 자동 관리 객체의 클래스는 힙 / 프리 스토어 메모리에 생성 된 다른 객체를 래핑합니다. 따라서 자동 관리 개체의 생성자가 실행되면 래핑 된 개체가 힙 / 프리 스토어 메모리에 생성되고 자동 관리 개체의 핸들이 범위를 벗어나면 해당 자동 관리 개체의 소멸자가 자동으로 호출됩니다. 개체는 삭제를 사용하여 파괴됩니다. OOP 개념을 사용하면 이러한 개체를 개인 범위의 다른 클래스 안에 래핑하면 래핑 된 클래스 멤버 및 메서드 및 메서드에 액세스 할 수 없습니다. 이것이 스마트 포인터 (핸들 클래스라고도 함)가 설계된 이유입니다. 이러한 스마트 포인터는 노출 된 메모리 개체가 구성되는 모든 멤버 / 메서드를 호출 할 수 있도록 허용하여 래핑 된 개체를 형식화 된 개체로 외부 세계에 노출합니다. 스마트 포인터에는 다양한 요구 사항에 따라 다양한 맛이 있습니다. 이에 대한 자세한 내용은 Andrei Alexandrescu의 Modern C ++ 프로그래밍 또는 boost 라이브러리 (www.boostorg)의 shared_ptr.hpp 구현 / 문서를 참조해야합니다. 이것이 RAII를 이해하는 데 도움이되기를 바랍니다. 이에 대한 자세한 내용은 Andrei Alexandrescu의 Modern C ++ 프로그래밍 또는 boost 라이브러리 (www.boostorg)의 shared_ptr.hpp 구현 / 문서를 참조해야합니다. 이것이 RAII를 이해하는 데 도움이되기를 바랍니다. 이에 대한 자세한 내용은 Andrei Alexandrescu의 Modern C ++ 프로그래밍 또는 boost 라이브러리 (www.boostorg)의 shared_ptr.hpp 구현 / 문서를 참조해야합니다. 이것이 RAII를 이해하는 데 도움이되기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.