- Item 8: new와 delete가 쓰임에 따른 의미들의 차이를 이해하라.
보통 C++에서 용어들을 정확히 이해 못할 경우가 있다. 바로
newoperator와
operator new가 그 대표적인 예가 될수있을 것이다. 다음의 코드를 보자
~cpp
string *ps = new string("Memory Management");
이 코드는 new operator를 사용한 것이다. new operator는 sizeof 처럼 언어 상에 포함되어 있으며, 개발자가 더 이상 그 의미의 변경이 불가능하다. 이건 두가지의 역할을 하는데, 첫째로 해당 객체가 들어갈 만한 메모리를 할당하는 것이고, 둘째로 해당 객체의 생성자를 불러주는 역할이다. new operator는 항상 이 두가지의 의미라 작동하며 앞에서 언급한듯 변경은 불가능하다.
보통 operator new는 이렇게 선언되어 있다.
~cpp
void * operator new(size_t size);
이건 과거 C에서의 malloc처럼 초기화 되지 않은 size만큼의 메모리를 할당해서 그걸 가리키는 void형의 pointer를 돌려주는 것이라고 예측할수 있겠다.(맞다) 개발자는 operator new를 overload할수 있지만 첫번째 인자는 항상 size_t가 되어야 한다.
아마 여러분은 operator new를 직접 부르는걸 결코 원하지 않겠지만(생성자, 파괴자를 생각해 보면 말이지), 써먹을수 있는데 한번 해보자.
~cpp
void *rawMemory = operator new(sizeof(string));
string 객체가 필요로 하는 메모리 만큼이 할당된다. 하지만 위의 의미처럼 malloc, operator new는 생성자를 호출하지는 앖는다. 생성자의 호출은 new operator 몫이다.
new operator를 보면
~cpp
string *ps = new string("Memory Management");
다음과 같은 코드는 컴파일러 단에서 이렇게 교체된다고 볼수 있다.
~cpp
void *memory = operator new(sizeof(string));
call string::string("Memory Management") on *memory;
string *ps = static_cast<string*>(memory);
해당 코드 두번째 부분의 생성자 호출을 눈여겨 봐라.
당신은 생성자를 직접 호출하기를 원할때가 있을 것이다. 하지만 생성자는 객체(object)를 초기화 시키고, 객제(object)는 오직 처음 주어지는 단 한번의 값으로 초기화 되어 질수있기 때문에 (예-const 인수들 초기화에 초기화 리스트를 쓰는 경우) 생성자를 이미 존재하고 있는 객체에 호출한다는건 생각의 여지가 없을 것이다.
그렇지만 여러분이 raw memory로 객체를 할당한다면 초기화 루틴이 필요하다.
(여담-후후 나도 상단의 operator new를 보는 순간 이 생각했다.)
바로 operator new의 특화 버전인 placement new로 할수 있다.
다음은 placement new를 사용한 예제이다.
~cpp
cass Widget {
public:
Widget(int widgetSize);
...
};
Widget* constructWidgetInBuffer(void * buffer, int widgetSize)
{
return new(buffer) Widget(widgetSize);
}
해당 함수(construcWidgetInBuffer())는 버퍼에 만들어진 Widget 객체의 포인터를 반환하여 준다. 이렇게 호출할 경우 객체를 임의 위치에 할당할수 있는 능력 때문에 shared memory나 memory-mapped I/O 구현에 용이하다 constructorWidget에 보이는건 바로
~cpp
return new (buffer) Widget(widgetSize);
이 문인데 , 아마 처음 보기에 생소할 것이다. 자세히 뜯어 보면, (buffer)에 의해서 암시적으로 new는 operator new로 호출되어 진다. 덧붙여, 아래의 void*는 메모리 상의 위치를 size_t는 메모리상 객체가 차지하는 영역이다. 자, 위와 비교해 보라 이 operator는 new를 overload한 버전이다.
~cpp
void* operator new(size_t, void *location)
{
return location;
}
이거 간단히 보이지만 placement new의 전부이다. operator new의 역할은 해당 객체를 위한 메모리를 찾고(할당), 해당 포인터의 반환이고 placement new의 경우에는 호출자가 이미 메모리를 확보하였고, 단순히 포인터 반환만 해준다. 모든 placement new가 반드시 이런 pointer의 전달 역할을 한다. 그리고 size_t 인자가 아무런 이름이 없어도 반항 안한다. 자세한건 Item 6을 보면 이해가 갈것이다.
이런 placement new는 C++ 표준 라이브러리의 한 부분으로 placement new를 쓰고자 한다면 #include<new> 를 해주면 된다.
자자 new 결론이다.
the
new operator : heap 메모리 확보와 생성자 호출 동시에
operator new : 해당 객체의 메모리 확보만
placement new : 이미 확보 메모리가 존재하고 객체 초기화를 원한다면
이다 어설프게 정리했고 차후 추가 할것이다.
- Deletion and Memory Deallocation
동적 할당에 대하여 리소스 새는걸 방지할려면 해당 동적 할당에 상응하는 응징(?)이 필요할 것이다. 예를들어서 new나 operator new에는 이런거
~cpp
string *ps;
...
delete ps; // delete operator 를 사용한다.
당신이 쓰고 있는 컴파일러는 객체 ps point를 파괴하고 객체가 가지고 있는 메모리를 해제한다.
메모리 해제(deallocaion)은 operator delete함수에 행해지는데 일반적으로 이렇게 선언되어 있다.
~cpp
void operator delete(void * memoryToBeDeallocated);
그러므로
~cpp
delete ps;
는 이런 코드가 수행된다고 보면 된다.
~cpp
ps->~string();
operator delete(ps);
(작성자 주: 이걸로 new와 delete의 환상,신비성이 깨졌다. )
그리고 이것의 의미는 당신이 초기화 되지 않은 raw로의 사용의 경우에는 new와 delete operator로 그냥 넘겨야 한다는 의미가 된다. 즉 이코드 대신에 다음의 예를 볼수 있을 것이다.
~cpp
void * buffer = operator new(50*iszeof(char));
...
operator delete(buffer);
위의 코드는 C++상에서 malloc 과 free 를 호출 것과 동일한 역할을 한다.
그렇다면, 이번에 당신은 placement new를 사용해서 메모리상에 객체를 만들었다면 delete를 사용할수 없을 꺼라는 예측을 할수 있을 것이다. 자 다음 코드를 보면서 명시적인 delete를 행하는 코드들을 보자
~cpp
void * mallocShared(size_t size);
void freeShared(void *memory);
void *shareMemory = mallocShared(sizeof(Widget));
Widget *pw = constructWidgetInBuffer(shareMemory, 10); // placement new이닷!
...
delete pw; // 이렇게 해서는 안된다. sharedMemory는 mallocShared에서 할당되는데 어찌 할수 있으리요.
// 그렇다면 다음과 같은 코드로 해야 한다.
pw->~Widget(); // 먼저 임의로 파괴자를 호출한다. 이렇게 하면 pw과 관련된 자원을 반환할것이고.
freeShared(pw); // 최종적으로 pw가 할당된 메모리 영역을 해제 시킨다.
별다른 특별한 지적사항이 없다. 여태 까지의 연장선이고 단 new를 이용한 배열 할당을 삭제할 경우
~cpp
string *ps = new string[10];
delete [] ps;
와 같이 짝을 맞추어 주어야 한다. placement는 당근 위의 지시 사항에 따르는 것이고 operator delete는 item 6에서와 같이 for문으로 순회하면서 지워 주어야 한다.
마지막으로 여기서 보다 시피 new와 delete를 만드는 자체는 당신이 조정 할수 없는 영역에 존재하지만 메모리 할당은 당신의 손아래 있다. new와 delete를 최적화나 수정 할때 꼭 기억해라 당신이 정말로 그걸 할수 없는가에 관해서 말이다. 당신은 그것들의 방법(new,delete메모리 할당 방법)은 변경할수 있다. 그러나 그들은 언어에 의해서 규정되어 져 있는 영역이다.