16: new 및 delete를 사용할 때는 형태를 반드시 갖추자
string* stringArray = new string[100];
...
delete stringArray
위의 코드는 문제가 없는 것 처럼 보이지만 큰 문제가 있다. stringArray가 가리키는 객체 중 99개는 정상적인 소멸 과정을 거치지 못할 가능성이 크다. 위의 코드는 소멸자가 99번 불릴 턱이 없기 때문이다.
new 연산자의 내부 동작은 일단 메모리가 할당되고, 할당된 메모리에 대해 한 개 이상의 생성자가 호출된다. delete의 내부 동작은 우선 기존에 할당된 메모리에 대해 한 개 이상의 소멸자가 호출되고, 그 후체 그 메모리가 해제된다. 여기서 delete 연산자가 적용되는 객체는 대체 몇 개 일까?
new로 힙에 만들어진 단일 객체의 메모리 배치구조와 객체 배열의 메모리 배치구조는 다르다. 특히, 배열을 위해 만들어지는 힙 메모리에는 대개 배열원소의 개수가 박혀 들어간다. 이 때문에 delete 연산자는 소멸자가 몇 번 호출될지를 쉽게 알 수 있다. 반면, 단일 객체용 힙 메모리는 이런 정보가 없다.
어떤 포인터에 대해 delete를 적용할 때, 배열 크기 정보가 있다는 정보를 알려주려면 '[ ]'를 delete 뒤에 붙여주는 것이다. 그제야 delete가 포인터가 배열을 가리키고 있구나라고 가정하게 된다. 그렇지 않으면 단일 객체로 간주한다.
string* stringPtr1 = new string;
string* stringPtr2 = new string[100];
...
delete stringPtr1;
delete[] stringPtr2;
'[ ]'를 사용하지 않으면 미정의 동작이 나타난다.
동적 할당된 메모리에 대한 포인터를 멤버 데이터로 갖고 있는 클래스를 만드는 중이라면, 이 클래스에서 제공하는 생성자도 여러 개일 경우에 특히 이 규칙을 뼛속 깊이 간직해야 한다. 왜냐하면 포인터 멤버를 초기화하는 부분인 생성자에서 new 형태를 똑같이 맞출 수 밖에 없기 때문이다.
이 규칙은 typedef를 사용할 때도 알아둘 필요가 있다.
typedef string AddressLines[4];
string* pal = new AddressLines;
delete pal; // 미정의 동작
delete[] pal; // 깔끔 마무리
머리 속이 심난해지지 않으려면, 배열 타입을 typedef 타입으로 만들지 않는 것이 좋다. string이나 vector를 사용하자. AddressLInes를 vector<string> 타입으로 만드느 것이다.
이것만은 잊지 말자!
- new 표현식에 []를 썼으면, 대응되는 delete 표현식에도 []를 써야 한다. 마찬가지로 new 표현식에 []를 안 썼으면 대응되는 delete 표현식에도 []를 쓰지 말아야 한다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 18: 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자 (0) | 2023.10.15 |
---|---|
[Effective C++] 17: new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자 (0) | 2023.10.14 |
[Effective C++] 15: 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 (2) | 2023.10.12 |
[Effective C++] 14: 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 (1) | 2023.10.10 |
[Effective C++] 13: 자원 관리에는 객체가 그만! (0) | 2023.10.09 |