Item 49 : Understand the behavior of the new-handler
new 처리자의 동작원리를 제대로 이해하자
operator new는 메모리 할당 요청을 만족하지 못하면, 예외를 발생시킨다.
오랫동안, 그것은 null pointer를 리턴했다.
operator new를 throw하기 전에, new-handler라는 클라이언트가 지정한 error-handling function을 호출할 수 있다.
out-of-memory-handling function을 지정하기 위해, 클라이언트는 std의 set_new_handler를 호출한다.
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
new_handler는 typedef for a pointer to a function이며 함수는 파라미터도 없고, 리턴값도 없다.
throw()는 함수가 어떤 예외도 던지지 않을 것임을 의미한다.
set_new_handler 함수의 파라미터는 메모리 할당 실패시 operator_new가 호출할 함수의 포인터이다.
리턴값은 set_new_handler가 호출되기 이전에 new_handler로 쓰이던 함수 포인터이다.
set_new_handler는 다음과 같이 사용할 수 있다.
void outOfMem()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem);
int *pBigDataArray = new int[1000000000L];
}
만약 operator new가 array를 위한 메모리를 할당할 수 없으면, outOfMem이 호출되고 error message가 출력된 후 프로그램이 종료될 것이다.
new_handler의 올바른 설계 방법은 다음과 같다.
1. Make more memory available
프로그램이 시작할 때, 메모리 블록을 크게 할당한 후, new_handler가 처음 호출될 때 그 메모리를 사용할 수 있도록 한다.
2. Install a different new-handler
현재 new-handler가 어떤 가능한 메모리 공간을 만들지 못하면, 다른 new-handler를 설치한다.
3. Deinstall the new-handler
set_new_handler에 null pointer를 넘긴다.
new_handler가 없으면, operator new는 에러처리함수가 없으면, 예외를 던진다.
4. Throw an exception of type bad_alloc
bad_alloc또는 bad_alloc에서 derived된 타입의 예외를 던진다.
이 예외는 메모리 할당을 호출한쪽으로 예외 전이된다.
5. Not return
abort또는 exit을 호출하여 종료한다.
할당된 객체의 클래스에 따라, 다른 방식으로 메모리 할당 실패를 다루고자 하는 경우, 클래스에서 자신의 set_new_handler와 operator new를 제공하도록 한다.
class Widget{
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size)throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
}
std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
Widget의 operator new가 할 일은 다음과 같다.
1. standard set_new_handler에 Widget의 new-handler를 넘긴다.
즉, global new-handler로 Widget의 new-handler를 설정한다.
2. 실제 메모리 할당을 위해 global operator new를 호출한다.
만약, 할당에 실패하면, operator new는 Widget의 new-handler를 호출하고, 궁극적으로 메모리 할당을 할 수 없으면, bad_Alloc을 throw한다.
이 경우에, Widget의 operator_new는 original new-handler를 다시 저장해야 하고, 예외를 전파해야 한다.
3. 만약, global operator new가 Widget 객체를 위한 충분한 메모리 할당을 할 수 있다면, Widget의 operator new는 할당된 메모리에 대한 포인터를 리턴한다.
global new-handler를 관리하는 객체의 소멸자는 자동으로 전역 new-handler를 Widget의 operator new에 대한 호출 이전의 상태로 복원한다.
class NewHandlerHolder{
public:
explicit NewHandlerHolder(std::new_handler nh)
: handler(nh){}
~NewHandlerHolder(){std::set_new_handler(handler);}
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size);
}
정리하면, set_new_handler함수를 쓰면 메모리 할당 요청이 만족되지 못했을 때 호출되는 함수를 지정할 수 있다.
예외불가(nothrow) new는 영향력이 제한되어 있다. 메모리 할당 자체에만 적용되기 때문이다. 이후에 호출되는 생성자에서는 얼마든지 예외를 던질 수 있다.
Item 50 : Understand when it makes sense to replace new and delete
new, delete를 언제 교체해야 좋을지 파악하자.
왜 컴파일러가 제공하는 operator new와 operator delete를 교체해야 하는가?
1. To detect usage errors
new로 메모리를 할당한 객체는 delete를 하지 않으면 메모리 누수 문제가 발생한다.
또한, new로 할당한 메모리에 delete를 중복하여 호출하면 의도하지 않은 동작이 발생할 수 있다.
Custom operator new는 known byte pattern('signatures')들을 넣을 공간을 만들만큼 block을 할당할 수 있다.
그러면, operator delete는 signatures가 온전한지 확인하여, 만약 온전하지 않다면 overrun이나 underrun이 발생했음을 확인할 수 있고, operator delete는 그 사실에 대한 로그를 남길 수 있다.
2. To improve efficiency
operator new와 operator delete는 general-purpose use를 위해 설계되었다.
따라서, 모든 것에 잘 작동하지만, 모든 것에 최적으로 동작하는 것은 아니다.
default operator new/delete를 능가하는 custom version을 종종 찾을 수 있다.
속도는 10의 몇 제곱으로, 메모리는 최대 50%의 성능을 향상시킬 수 있다.
3. To collect usage statistics about the use of dynamically allocated memory
동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해 사용할 수 있다.
4. To increase the speed of allocation and deallocation
General-purpose allocators는 종종 custom version보다 매우 느리다.
특히 특정 타입의 객체에 대해 custom version이 설계되었을 경우 차이가 극명하다.
싱글 스레드로 동작하는 프로그램에서는 thread-safe로 동작하는 default memory management routine이 비효율적이다.
thread-unsafe allocators를 작성함으로써 속도를 향상시킬 수 있다.
5. To reduce the space overhead of default memory management
General-purpose allocators는 더 많은 메모리를 할당한다.
각각의 할당된 블록에 overhead가 발생한다.
작은 크기의 객체에 대해 튜닝된 allocator를 사용하면 overhead를 제거할 수 있다.
6. To compensate for suboptimal alignment in the default behavior
x86 architecture에서 double은 8바이트 단위로 정렬되어 있을 때 접근 속도가 가장 빠르다.
그러나 몇몇 컴파일러는 double의 dynamic allocation에 대해 정렬을 보장하지 않는다.
몇몇 경우에, 8바이트 단위 정렬을 보장하는 custom version으로 바꾸면 프로그램 성능이 크게 향상할 수 있다.
7. To cluster related objects near one another
만약 특정한 자료구조가 일반적으로 함께 사용되고, 데이터에 대해 작업을 수행할 때, page faults가 발생하는 빈도를 최소화하기를 원한다면, 자료구조에 대한 분리된 heap을 만들어 가능한 적은 page에 군집화되도록 하는것이 타당하다.
Placement new와 delete를 통해 메모리 군집화를 쉽게 구현할 수 있다.
8. To obtain unconventional behavior
때때로 컴파일러가 제공하는 버전은 제공하지 않는 무언가를 operator new와 delete가 수행하기를 원한다.
예를 들어, 공유 메모리에 할당하고자 하는 경우가 있다.
Item 51 : Adhere to convention when writing new and delete
new및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아두자.
1. operator new
operator new는 메모리 할당을 시도하는 infinite loop를 포함해야 하며 만약 메모리 요청을 만족하지 못하는 경우, new-handler를 호출해야 하고, zero bytes에 대한 요청을 다루어야 한다.
Class-specific version은 예상보다 큰 크기의 블록에 대한 요청을 다루어야 한다.
2. operator delete
operator delete는 만약 pointer가 null인 경우에는 아무것도 하면 안된다.
Class-specific version은 예상보다 큰 사이즈의 블록을 처리할 수 있어야 한다.
Item 52 : Write placement delete if you write placement new
위치지정 new를 작성한다면 위치지정 delete도 같이 준비하자.
operator new의 placement version을 작성해야 할때, 이에 대응되는 operator delete의 placement version을 작성해야 한다.
만약 그렇지 않으면, 프로그램은 미묘하고 간헐적인 메모리 누수 문제를 겪을 것이다.
new와 delete의 placement version을 선언할 때, 의도하지 않게 이 함수들의 normal version을 숨기지 않아야 한다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] Chapter 7: Templates and Generic Programming(2) (0) | 2022.07.13 |
---|---|
[Effective C++] Chapter 7: Templates and Generic Programming(1) (0) | 2022.07.09 |
[Effective C++] Chapter 6: Inheritance and Object-Oriented Design(2) (0) | 2022.07.08 |
[Effective C++] Chapter 6: Inheritance and Object-Oriented Design(1) (0) | 2022.07.06 |
[Effective C++] Chapter 5: Implementations(2) (0) | 2022.07.06 |