일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 영화
- 티스토리챌린지
- more effective c++
- 다형성
- UE4
- resource management class
- 상속
- operator new
- c++
- 영화 리뷰
- 게임
- 언리얼
- 참조자
- 반복자
- Effective c++
- virtual function
- 비교 함수 객체
- exception
- implicit conversion
- Smart Pointer
- lua
- 암시적 변환
- effective stl
- 예외
- 오블완
- 메타테이블
- 루아
- reference
- Vector
- 스마트 포인터
Archives
- Today
- Total
스토리텔링 개발자
[More Effective C++] 8. new / delete 연산자와 operator new / delete 본문
개발/More Effective C++
[More Effective C++] 8. new / delete 연산자와 operator new / delete
김디트 2024. 8. 8. 11:12728x90
항목 8 : new와 delete의 의미를 정확히 구분하고 이해하자
‘new 연산자’와 ‘operator new’의 차이
string* ps = new string("Memory Management"); // new 연산자가 사용되었다.
- new 연산자
- C++에서 기본으로 제공한다.
- sizeof가 그런 것처럼, 동작 원리를 바꾸는 것이 불가능하다.
- 동작 단계
- 요청 타입의 객체를 담을 수 있는 크기의 메모리를 할당한다.(이 동작이 operator new 함수 호출로 동작된다.)
- 객체의 생성자를 호출하여 할당된 메모리의 객체 초기화를 수행한다.
- operator new
- 객체로 담을 메모리를 할당하는 방법이다.
- new 연산자는 필수적인 메모리 할당을 위해 operator new를 호출하게 되어 있다.
operator new의 형태
void* operator new(size_t size);
- 반환 타입 void*
- 이 함수는 초기화되지 않은 원시 메모리의 포인터를 반환한다.
- 초기화하고 반환하도록 커스텀할 수도 있겠지만, 대개 그렇게 하지 않는다.
- size_t 매개변수
- 할당된 메모리의 크기를 지정한다.
- 매개변수를 추가한 버전을 오버로딩(위치지정 new) 할 수 있다.
- 하지만 첫번째 매개변수는 항상 size_t여야 한다.(위치지정 new)
- 위치지정 new(Effective C++ 항목 52 참조)
operator new의 특징
- operator new는 직접 호출할 수도 있다.
void* rawMemory = operator new(sizeof(string)); // operator new 직접 호출
- operator new는 메모리만 할당한다.
- malloc과 마찬가지의 역할만 맡는다.
- 그러므로 어떤 생성자를 호출해줘야 할 지는 알 방도가 없다.
- 할당된 메모리를 받아서 객체 구실을 할 수 있도록 하는 것은 new 연산자의 일이다.
// 컴파일러가 다음 문장을 만난다면?
string* ps = new string("Memory Management");
// 아래와 비슷한 코드를 만들어낼 것이다.
void* memory = operator new(sizeof(string)); // 1. raw 메모리 획득
... // 2. *memory에 대해 string::string("Memory Management")를 호출한다.(객체를 초기화한다.)
string* ps = static_cast<string*>(memory): // 3. ps가 새 객체를 가리키게 한다.
- 위 예제의 2번 과정(raw 메모리에 대해 생성자 호출 단계)은 유저가 손 댈 수 있는 부분이 아니다.
- 그러므로 객체를 힙에 할당하려면 방법은 컴파일러가 제공하는 new 연산자를 사용하는 법 뿐이다.
- 이렇게 유저가 손댈 수 없는 성질의 것은 vtbl도 포함된다.(항목 24 참조)
위치지정 new(placement new)
- 그럼에도 미초기화 메모리가 덩그러니 있고, 생성자를 호출해주고 싶은 상황이 있을 수 있다.
- 그 경우에는 위치지정 new를 사용하여 new 연산자 로직을 통과시켜 객체를 초기화 시켜줄 수 있다.
class Widget
{
public:
Widget(int widgetSize);
...
};
// 위치지정 new로 raw 메모리를 그저 반환해주는 operatr new 를 만든다.
// size_t는 사용하지 않으므로 이름이 없도록 처리한다.(항목 6 참조)
// #include <new>로 포함하면 위치지정 new를 아래처럼 직접 구현할 필요조차 없다.
void* operator new(size_t, void* location)
{
return location;
}
Widget* constructWidgetInBuffer(void* buffer, int widgetSize)
{
// 위치지정 new를 통해 new 연산자 로직을 통과시킬 수 있다.
return new (buffer) Widget(widgetSize);
}
객체 삭제와 메모리 해제(deletion & deallocation)
- delete 연산자 / operator delete 역시 new의 경우와 동일하다.
void operator delete(void* memoryToBeDeallocated); // operator delete
string* ps;
...
delete ps; // delete 연산자를 사용한다.
// delete ps; 에 대해 컴파일러는 아래와 비슷한 코드를 만들어낸다.
ps->~string(); // 소멸자 호출
operator delete(Ps); // operator delete 함수 호출로 메모리 해제
유의사항
- raw 메모리만으로 무언가를 할 때는?
- new / delete 연산자를 건너뛰고 operator new / delete를 사용해야 한다.
- 즉 operator new / delete는 C++ 버전의 malloc / free인 셈이다.
- 그 이유는, 생성자, 소멸자 호출이 불필요하기 때문이다.
void* buffer = operator new(50 * sizeof(char)); // 50개 문자를 담는 메모리 할당
...
operator delete(buffer); // 소멸자 호출 없이 메모리 해제
- 위치지정 new를 사용한 메모리에는 절대로 delete 연산자를 쓰지 말아야 한다.
- 원칙적으로 operator new로 할당된 것이 아니기 때문이다.
- 사용한 그 메모리 포인터가 어디에서 왔는지는 유저만이 알 것이다.
- 즉, 생성자가 한 일을 돌려놓기만 하되(소멸자만 호출하되), 메모리 해제는 불필요하다.
// 공유 메모리에 메모리 할당, 해제 함수
void* mallocShared(size_t size);
void freeShared(void* memory);
void* sharedMemory = mallocShared(sizeof(Widget)); // 공유 메모리 할당
Widget* pw = constructWidgetInBuffer(sharedMemory, 10); // 위치지정 new로 생성자 호출
...
delete pw; // 미정의 결과!!
pw->~Widget(); // 소멸자만 호출한 후,
freeShared(pw); // 공유 메모리 해제(문제 없음)
배열 할당의 경우
string* ps = new string[10]; // 객체 배열을 할당한다.
// 이 경우 new 연산자는 어떻게 동작할까?
- 배열 new 연산자와 단일 객체 new 연산자의 차이점
- 메모리를 할당할 때 배열 할당용 버전인 operator new[]를 호출한다.("array new")
- 오버로딩이 가능하다.
- 허나, operator new[]를 지원하지 않는 컴파일러 환경에서는, 오버로딩을 매우 매우 신중하게 생각해야 한다.
- 이 경우 operator new의 전역 함수 버전을 사용하여 배열을 할당한다.
- 헌데 operator new 전역 함수 버전을 다시 작성한다는 건 절대 가벼운 일이 아니다.
- operator new의 함수 매커니즘이 변경되면 어떤 이슈가 생길지 예측할 수 없으므로.
- 더군다나 다른 라이브러리와 호환되지 않게 된다.(항목 27 참조)
- 생성자를 호출하는 횟수가 더 많다.
- 배열 요소에 대해 일일이 생성자를 호출하기 때문이다.
- 메모리를 할당할 때 배열 할당용 버전인 operator new[]를 호출한다.("array new")
string* ps = new string[10]; // new 연산자. operator new[] 호출 후 각 요소에 생성자 호출
...
delete[] ps; // delete 연산자. 각 요소에 소멸자 호출 후 operator delete[] 호출
결론
- new / delete 연산자는 C++에서 기본으로 제공되는 것이므로 바꿀 수 없다.
- 하지만 내부적으로 사용하는 메모리 할당, 해제 함수는 고칠 수 있다.(operator new / delete)
참조
728x90
'개발 > More Effective C++' 카테고리의 다른 글
[More Effective C++] 10. 생성자 예외 처리 (0) | 2024.08.12 |
---|---|
[More Effective C++] 9. 자원 관리 객체(RAII) (0) | 2024.08.09 |
[More Effective C++] 7. operator&& / operator|| / operator, (0) | 2024.08.07 |
[More Effective C++] 6. operator++ / operator-- (0) | 2024.08.06 |
[More Effective C++] 5. 암시적 변환 지양하기 (0) | 2024.08.05 |
Comments