C ++의 경우 "수동 메모리 관리를 수행해야합니다"라는 일반적인 오해라는 점에 유의해야합니다. 사실, 코드에서는 일반적으로 메모리 관리를 수행하지 않습니다.
고정 크기 객체 (범위 수명)
객체가 필요한 대부분의 경우 객체는 프로그램에서 정의 된 수명을 가지며 스택에 생성됩니다. 이것은 모든 내장 기본 데이터 유형뿐만 아니라 클래스 및 구조체의 인스턴스에서도 작동합니다.
class MyObject {
public: int x;
};
int objTest()
{
MyObject obj;
obj.x = 5;
return obj.x;
}
함수가 끝나면 스택 객체가 자동으로 제거됩니다. Java에서 오브젝트는 항상 힙에 작성되므로 가비지 콜렉션과 같은 메커니즘으로 제거해야합니다. 이것은 스택 객체의 문제가 아닙니다.
동적 데이터를 관리하는 객체 (범위 수명)
스택에 공간을 사용하면 고정 된 크기의 객체에 적합합니다. 배열과 같이 가변적 인 공간이 필요한 경우 다른 접근 방식이 사용됩니다. 목록은 동적 메모리를 관리하는 고정 크기의 개체로 캡슐화됩니다. 이것은 객체가 특별한 정리 기능인 소멸자를 가질 수 있기 때문에 작동합니다. 객체가 범위를 벗어나고 생성자의 반대 작업을 수행하면 호출됩니다.
class MyList {
public:
// a fixed-size pointer to the actual memory.
int* listOfInts;
// constructor: get memory
MyList(size_t numElements) { listOfInts = new int[numElements]; }
// destructor: free memory
~MyList() { delete[] listOfInts; }
};
int listTest()
{
MyList list(1024);
list.listOfInts[200] = 5;
return list.listOfInts[200];
// When MyList goes off stack here, its destructor is called and frees the memory.
}
메모리가 사용되는 코드에는 메모리 관리가 전혀 없습니다. 우리가 확인해야 할 유일한 것은 우리가 작성한 객체가 적절한 소멸자를 가지고 있다는 것입니다. 우리가 범위를 벗어나는 방식에 관계없이 listTest
예외를 통해 또는 단순히 반환하여 소멸자 ~MyList()
가 호출되며 메모리를 관리 할 필요가 없습니다.
( 이진 NOT 연산자 를 사용하여 ~
소멸자를 표시하는 것은 재미있는 디자인 결정이라고 생각합니다 . 숫자에 사용하면 비트가 반전됩니다. 유사하게는 생성자가 수행 한 작업이 반전되었음을 나타냅니다.)
기본적으로 동적 메모리가 필요한 모든 C ++ 객체는이 캡슐화를 사용합니다. RAII ( "자원 획득은 초기화")라고 불리며, 이는 객체가 자신의 컨텐츠에 관심을 갖고 있다는 간단한 아이디어를 표현하는 아주 이상한 방법입니다. 그들이 얻는 것은 청소하는 것입니다.
다형성 개체 및 범위를 벗어난 수명
이제이 두 경우 모두 수명이 명확하게 정의 된 메모리에 대한 것입니다. 수명은 범위와 같습니다. 스코프를 떠날 때 객체가 만료되지 않게하려면 메모리를 관리 할 수있는 세 번째 메커니즘 인 스마트 포인터가 있습니다. 스마트 포인터는 런타임에 유형이 다르지만 공통 인터페이스 또는 기본 클래스가있는 객체 인스턴스가있는 경우에도 사용됩니다.
class MyDerivedObject : public MyObject {
public: int y;
};
std::unique_ptr<MyObject> createObject()
{
// actually creates an object of a derived class,
// but the user doesn't need to know this.
return std::make_unique<MyDerivedObject>();
}
int dynamicObjTest()
{
std::unique_ptr<MyObject> obj = createObject();
obj->x = 5;
return obj->x;
// At scope end, the unique_ptr automatically removes the object it contains,
// calling its destructor if it has one.
}
std::shared_ptr
여러 클라이언트간에 객체를 공유하기위한 또 다른 종류의 스마트 포인터가 있습니다. 마지막 클라이언트가 범위를 벗어날 때만 포함 된 개체를 삭제하므로 클라이언트가 몇 개인 지 얼마나 오래 사용할지 완전히 알 수없는 상황에서 사용할 수 있습니다.
요약하면, 실제로 수동 메모리 관리를 수행하지 않는 것으로 나타났습니다. 모든 것이 캡슐화되고 완전히 자동적 인 스코프 기반 메모리 관리를 통해 관리됩니다. 이것으로 충분하지 않은 경우, 원시 메모리를 캡슐화하는 스마트 포인터가 사용됩니다.
delete
예외가 발생했을 때 관리하기가 거의 불가능하고 일반적으로 안전하게 사용하기 어렵 기 때문에 원시 포인터를 C ++ 코드의 어디에서나 리소스 소유자로 사용하는 것, 생성자 외부의 원시 할당 및 소멸자 외부의 원시 호출 을 사용하는 것은 매우 나쁜 습관으로 간주 됩니다.
최고 : 모든 유형의 리소스에 적용
RAII의 가장 큰 장점 중 하나는 메모리에만 국한되지 않는다는 것입니다. 실제로 파일과 소켓 (열기 / 닫기)과 같은 리소스와 뮤텍스 (잠금 / 잠금 해제)와 같은 동기화 메커니즘을 관리하는 매우 자연스러운 방법을 제공합니다. 기본적으로 수집 및 해제해야하는 모든 리소스는 C ++에서 정확히 동일한 방식으로 관리되며이 관리는 사용자에게 맡겨지지 않습니다. 생성자에서 획득하고 소멸자에서 해제하는 클래스로 모두 캡슐화됩니다.
예를 들어, 뮤텍스를 잠그는 함수는 일반적으로 C ++에서 다음과 같이 작성됩니다.
void criticalSection() {
std::scoped_lock lock(myMutex); // scoped_lock locks the mutex
doSynchronizedStuff();
} // myMutex is released here automatically
다른 언어는 수동으로 (예를 들어 finally
절에서) 요구 하거나이 문제를 해결하는 특수 메커니즘을 생성하지만 특히 우아한 방법으로 (일반적으로 인생의 후반에 충분한 사람들이 단점으로 고통 받았다). 이러한 메커니즘은 Java에서 리소스 를 사용하여 시도 하고 C #에서 using 문을 사용 하며 둘 다 C ++의 RAII와 비슷합니다.
요약하자면,이 모든 것이 C ++에서 RAII에 대한 매우 피상적 인 설명 이었지만, 독자들이 C ++에서 메모리와 리소스 관리가 일반적으로 "수동"이 아니라 실제로 대부분 자동이라는 것을 이해하는 데 도움이되기를 바랍니다.