신입 게임 개발자의 프로그래밍 일기

[C++ 입문자에서 벗어나기]Chapter_4: 메모리(Memory)#4 - 1 본문

Language/C++

[C++ 입문자에서 벗어나기]Chapter_4: 메모리(Memory)#4 - 1

KFGD 2018. 1. 27. 14:07

[C++ 입문자에서 벗어나기]Chapter_4: 메모리(Memory)#4 - 1


여러분도 아시다시피 new는 재정의할 수 있습니다.

하지만 이 new를 재정의할 때도 따르는 관례들이 있다는 것을 알고 계셨나요?

이번 포스팅에서는 이 관례들에 대해서 이야기해보려고 합니다.


[new와 delete를 재정의할 때 따르는 관례들]


먼저 new를 재정의(Overloading)해봅시다!



std::bad_alloc을 던지는 new를 전역 범위와 클래스 전용 두가지로 오버로딩 해보았습니다.

실행시켜보면 정상적으로 문장 두개가 출력되는 것을 확인할 수 있습니다.

그런데 위 코드는 C++ 표준에서 권고하는 관례(가이드라인)에 적합한 new가 아닙니다.


1. 크기가 없는, 즉 0바이트 메모리 요청에 대한 대비를 할 것

2. 가용 메모리가 부족할 경우에는 new 처리자 함수를 호출할 것

3. 메모리 할당은 반복 루프 안에서 처리할 것

4. 상속 관계에서 실수로 기본(normal) 형태의 new가 가려지지 않도록 할 것.


위와 같은 관례들이 존재하는데 Effective C++ 항목 51에서 제시하는 코드 중 일부를 수정한 코드를 수정하여

위 관례에 나름(?) 맞게 코드를 작성해보았습니다.



전역 범위에서 new를 먼저 살펴보겠습니다.

가장 먼저 조건문 하나가 눈에 띕니다.

첫번째 관례인 크기가 0인 메모리 할당을 요청할 경우의 처리인데 요청 크기를 강제로 1로 바꿔버리는 코드입니다.

치사하다고 느껴질 수도 있지만 관례 1을 만족하기는 합니다.

(위 처리방식은 Effective C++ 항목 51에서 제공하는 방식입니다.)


그다음으로는 무한 반복문이 보입니다.

관례 3에 따라서 malloc으로 메모리를 할당하는 부분이 무한루프 안에 있음을 확인할 수 있습니다.


다음은 관례2 메모리 할당이 실패할 경우, new 처리자 함수를 호출하는 부분입니다.

처음 set_new_handler함수는 현재 설정되어 있는 new 처리자 함수(매개변수가 없는 void 반환 함수 포인터 형태)를 

얻어오기 위해 호출하고 그다음에는 해당 함수를 다시 설정하여 아무일 없었다는 듯이 기존 new 처리자 함수를 유지시킵니다.

이후, 처음 set_new_handler함수에서 얻어온 함수 포인터가 존재한다면 실행시키고 아니라면 std::bad_alloc 예외를 던집니다.


전역 범위의 new는 다 분석해봤으니 이제 클래스 전용 new를 살펴보겠습니다.

별다른 차이점이 없습니다. 다만 전역 범위와 다르게 size가 0인 메모리 요청이 왔을 때, 크기를 0으로 바꾸는 것이 아닌

클래스의 크기가 요청한 메모리 크기와 다를 경우에 전역 new를 호출하도록 되어 있습니다.

먼저 관례 4를 기준으로 살펴보겠습니다. 현재 코드 상에서는 CPerson을 상속받는 CDerivedPerson이라는 파생클래스가 존재합니다.

operator new는 상속이 되는 함수로 파생된 클래스에서는 원래의 전역 new를 사용하기를 원할 것입니다.

파생된 클래스에서도 부모 클래스의 new를 쓰고 싶을 수도 있잖아요!! 라고 이야기하신다면

Effective C++의 지문 중 일부로 대신 설명하겠습니다.


"특정 클래스 전용의 할당자를 만들어서 할당 효율을 최적화하기 위해서 사용자 정의 메모리 관리자를 작성할 수 있다는 이야기는 항목 50을 읽은 분이라면 기억하고 계실 것입니다. 여기서 특정 클래스란 '그' 클래스 하나를 가리킬 뿐, '그 클래스 혹은 그 클래스로부터 파생된 다른 클래스들' 모두를 통칭하는 것은 아닙니다. 그러니까 어떤 X라는 클래스를 위한 operator new 함수가 있따면, 이 함수의 동작은 크기가 sizeof(X)인 객체에 대해 맞추어져 있는 것입니다."(Effective C++ 3판 번역본, p.366 진문 중 일부 발췌)


보통 파생된 크기는 추가적인 멤버변수가 존재하기 때문에 sizeof로 현재의 클래스 전용으로 만듬으로써

기본으로 제공되는 new가 가려지지 않게 됩니다. 물론, 이 코드에서는 전역 new도 재정의했기 때문에

이상하다고 이야기하실 수도 있겠지만 이 점은 주제에 맞는 코드를 제공하기 위함임으로 너그럽게 넘어가주시기 바랍니다.


관례 4는 이야기했으니 관례 1을 이야기해보겠습니다. 

"클래스의 멤버 변수가 없으면 사이즈가 sizeof는 0이 되지 않나요?" 라고 질문을 하신다면 "아니요"라고 답해드리겠습니다.

클래스의 멤버 변수는 무조건 0이 될 수 없습니다. C++에서는 두 개의 다른 객체가 서로 다른 메모리 주소를 갖는 것을 보장하기 위해

크기가 0인 독립 구조의 객체가 생기는 것은 금지하기 떄문입니다.

보통 빈(empty) 클래스가 있다면 컴파일러는 char 하나를 넣어서 크기를 1로 바꾸어버립니다.

사이즈가 0인 클래스가 존재하지 않으니 요청한 메모리의 사이즈가 0일 경우는 고려하지 않아도 됨으로 관례 1을 만족하게됩니다.


new를 재정의할 떄 따르는 관례들은 전부 살펴보았으니 이번에는 delete를 재정의시 따르는 관례들을 살펴보겠습니다.

별 거 없습니다. 그저 단순히 널 포인터에 대한 delete 호출이 항상 안전하기만을 보장하면 됩니다.

간단히 샘플 코드를 제공하는 것으로 "new와 delete를 재정의할 때 따르는 관례들"에 대한 이야기를 마치도록 하겠습니다.



원래 계획대로라면 이번 포스팅에서 "new 재정의 유의사항: 이름가림"과 "new를 재정의하면 그에 맞는 delete를 재정의하자"에 대해서도

이야기해야겠지만 예상보다 많은 분량에 4-1, 4-2, 4-3 형태로 작성하는 것으로 계획을 변경하였습니다.

Comments