로또
16. new 및 delete를 사용할 때는 형태를 반드시 맞추자. 본문
다루는 내용
new - delete, new[] - delete[]를 통해 할당되는 메모리 구조를 보고 형태를 맞추어야 하는 이유를 알자.
new, delete, 생성자, 소멸자
string* string_array = new string[100];
delete string_array;
위와 같은 코드는 문제없어 보이지만 미정의 동작을 보이기에 충분하다.
string_array가 가리키는 100개의 string 객체들 가운데, 99개는 정상적인 소멸 과정을 거치지 못하기 때문이다. 왜냐하면 소멸자가 충분한 횟수만큼 호출되지 못하기 때문이다.
class Test{
public:
Test(){ cout << "Test Ctor" << endl; }
~Test(){ cout << "Test Dtor" << endl; }
};
위와 같은 클래스에 대해 다음과 같이 사용하면
int main(){
Test* tests = new Test[4]; // 4개의 생성자
delete tests; // 1개의 소멸자
}

이와 같은 실행 결과가 나타나며, 4개의 생성자가 호출된 것이 비해 소멸자는 1개만 호출된 모습을 볼 수 있다.
int main(){
Test* tests = new Test[4]; // 4개의 생성자
delete[] tests; // 4개의 소멸자
}
반대로, 올바르게 수정해주었을 때 결과이다.

우리가 new 연산자를 사용해서 어떤 객체를 동적 할당하면, 두 가지 내부 동작이 진행된다.
- operator new 를 통해 메모리가 할당된다.
- 할당된 메모리에 대해 한 개 이상의 생성자가 호출된다.
반대로, 우리가 delete 연산자를 사용하면 두 가지 내부 동작이 진행된다.
- 기존에 할당된 메모리에 대해 한 개 이상의 소멸자가 호출된다.
- operator delete를 통해 메모리가 해제된다.
이 때, delete 연산자가 적용되는 객체의 개수는 소멸자가 호출되는 횟수와 동일하다.
new로 생성된 하나의 객체와, new []로 생성된 객체 배열의 메모리 구조

배열을 위해 만들어지는 힙 메모리에는 대개 배열 원소의 개수가 함께 저장된다. 이 때문에, delete 연산자는 소멸자가 몇 번 호출되어야 하는지 알 수 있다.
반면, 단일 객체용 힙 메모리에는 그러한 정보가 없다.
즉, 어떤 포인터에 대해 delete를 적용할 때, delete 연산자로 하여금 "배열 크기 정보가 존재한다."는 것을 알려주기 위해서는 delete []를 사용해야 한다는 것이다. 그렇지 않으면 delete는 단일 객체라고 간주하고 1회의 소멸자만 호출될 것이다.
이것만은 잊지 말자!
- new 표현식에 []를 썼으면, 대응되는 delete 표현식에도 []를 써야 합니다.
- new 표현식에 []를 쓰지 않았으면, 대응되는 delete 표현식에도 []를 쓰지 말아야 합니다.
'책 > Effective C++' 카테고리의 다른 글
| 18. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자. (1) | 2023.11.14 |
|---|---|
| 17. new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자. (0) | 2023.11.13 |
| 15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자. (0) | 2023.11.13 |
| 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자. (0) | 2023.11.11 |
| 13. 자원 관리에는 객체가 그만! (0) | 2023.11.10 |