delete 표현식

delete 표현식 (delete expression)

delete 표현식은 new 표현식으로 이전에 할당한 객체(들)를 파괴하고, 확보했던 메모리 영역을 해제하는 역할을 해요. new로 만들었으니 delete로 되돌려 준다고 생각하면 돼요. 객체를 하나 지우는 delete와 배열을 지우는 delete[]가 있어요.

출처: cppreference

본문

문법 (Syntax)

표기 의미
::(선택) delete expression (1) 비배열 객체 하나 파괴
::(선택) delete[] expression (2) 배열 파괴
  • expression — 다음 중 하나: 객체 타입에 대한 포인터로 문맥상 암시적으로 변환 가능한 클래스 타입의 표현식, 또는 객체 타입에 대한 포인터의 prvalue.

  • 1) new 표현식으로 만든 비배열 객체 하나를 파괴해요.

  • 2) new[] 표현식으로 만든 배열을 파괴해요.

설명 (Explanation)

expression에서 (가능한 변환 후) 평가된 포인터를 ptr이라고 해요.

비배열 형태(1) 에서 ptr은 다음 중 하나여야 해요.

  • 널 포인터.
  • new 표현식으로 만든 비배열 객체에 대한 포인터.
  • new 표현식으로 만든 비배열 객체의 기반 부분 객체에 대한 포인터.

그리고 ptr이 가리키는 타입(pointed-to type)은 그 객체(또는 기반 부분 객체)의 타입과 유사(similar) 해야 해요. ptr이 그 밖의 다른 것이면, 배열 형태의 new 표현식으로 얻은 포인터라면 특히, 동작이 정의되지 않음이에요.

배열 형태(2) 에서 ptr은 널 포인터이거나, 그 값이 할당 함수가 비할당(non-allocating) 형태(즉 오버로드 (10))가 아니었던 배열 형태의 new 표현식으로 이전에 얻어진 포인터여야 해요. 그리고 ptr이 가리키는 타입은 배열 객체의 원소 타입과 유사해야 해요. 그 밖의 것, 특히 비배열 형태의 new 표현식으로 얻은 포인터라면 동작이 undefined예요.

delete 표현식의 결과 타입은 항상 void 예요.

삭제되는 객체가 삭제 지점에서 불완전 클래스 타입이고, 완전한 클래스가 비자명 소멸자나 할당 해제 함수를 가진다면, (until C++26) 동작이 undefined이고 (since C++26) 그 프로그램은 ill-formed예요.

ptr이 널 포인터가 아니고 할당 해제 함수가 파괴적 delete(destroying delete) 가 아니라면(C++20부터), delete 표현식은 파괴되는 객체, 또는 파괴되는 배열의 각 원소(배열의 마지막 원소에서 첫 원소 순서로) 에 대한 소멸자(있다면)를 호출해요. 그 소멸자는 delete 표현식이 나타나는 지점에서 접근 가능해야 해요.

그 다음에는, 어떤 소멸자가 예외를 던졌는지와 무관하게, delete 표현식은 할당 해제 함수(deallocation function) 를 호출해요. (C++14부터) 짝이 되는 new 표현식이 다른 new 표현식과 결합되지 않았다면, 비배열 형태는 operator delete(첫 번째 버전), 배열 형태는 operator delete[](두 번째 버전)이에요.

할당 해제 함수의 이름은 ptr이 가리키는 객체의 동적 타입의 스코프에서 검색돼요. 그래서 클래스별 할당 해제 함수가 있으면 전역 것보다 먼저 발견돼요. delete 표현식에 ::가 있으면, 이 검색은 전역 네임스페이스만 봐요. 어떤 경우에도, 보통의 할당 해제 함수 이외의 선언은 무시돼요.

할당 해제 함수가 발견되면, 호출할 함수는 다음과 같이 선택돼요 (이 함수들과 그 효과에 대한 더 자세한 설명은 할당 해제 함수 페이지 참고).

  • (C++20부터) 할당 해제 함수 중 하나라도 파괴적 delete면, 모든 비파괴적 delete는 무시돼요.
  • (C++17부터) 타입의 정렬 요구(alignment requirement)가 __STDCPP_DEFAULT_NEW_ALIGNMENT__를 초과하면, 정렬 인지(aligned) 할당 해제 함수(std::align_val_t 타입 매개변수 포함)가 우선돼요. 그 밖의 타입에는 정렬 비인지 할당 해제 함수(std::align_val_t 매개변수 없는)가 우선돼요. 우선 함수가 둘 이상 발견되면 다음 단계에서 우선 함수만 고려해요. 우선 함수가 없으면 다음 단계에서 비우선 함수를 고려해요. 함수가 하나만 남으면 그 함수를 선택해요.
  • 발견된 할당 해제 함수가 클래스별이라면, 크기 비인지(size-unaware) 클래스별 할당 해제 함수(std::size_t 매개변수 없는)가 크기 인지(size-aware) 함수(std::size_t 매개변수 있음)보다 우선돼요.
  • (C++14부터) 그 외에는 검색이 전역 스코프에 도달한 경우: 타입이 완전하고, 배열 형태에서만 피연산자가 비자명 소멸자나 (다차원일 수 있는) 그 배열을 가진 클래스 타입에 대한 포인터라면, 전역 크기 인지 전역 함수(std::size_t 매개변수)를 선택해요. 그 외에는 크기 인지 전역 할당 해제 함수를 선택할지 크기 비인지 전역 할당 해제 함수를 선택할지 불특정(unspecified) 이에요.

선택된 할당 해제 함수는 delete 표현식이 나타나는 지점에서 접근 가능해야 해요. 단 할당 해제 함수가 동적 타입의 가상 소멸자 정의 지점에서 선택된 경우는 예외예요.

회수할 저장 블록에 대한 포인터가 위 과정으로 선택된 할당 해제 함수에 첫 번째 인자로 전달돼요. 블록의 크기는 선택적인 std::size_t 인자로, 정렬 요구는 (C++17부터) 선택적인 std::align_val_t 인자로 전달돼요.

ptr널 포인터 값이면 소멸자는 호출되지 않고, 할당 해제 함수는 호출될 수도 있고 아닐 수도 있어요(불특정). 다만 기본 할당 해제 함수들은 널 포인터를 받으면 아무것도 하지 않는 것이 보장돼요.

ptrnew로 할당된 객체의 기반 클래스 부분 객체에 대한 포인터라면, 기반 클래스의 소멸자는 가상(virtual) 이어야 해요. 그렇지 않으면 동작이 undefined예요.

참고 (Notes)

void에 대한 포인터는 삭제할 수 없어요. void 포인터는 객체 타입에 대한 포인터가 아니기 때문이에요.

(C++11부터) delete 키워드 뒤의 대괄호 쌍은 항상 배열 형태로 해석되어요. 그래서 delete 바로 뒤에 빈 캡처 목록을 가진 람다 표현식이 오면 괄호로 감싸야 해요.

// delete []{ return new int; }(); // 구문 오류
delete ([]{ return new int; })();  // OK

키워드 (Keywords)

관련 키워드는 delete예요.

결함 보고 (Defect reports)

다음의 동작을 바꾸는 결함 보고가 이전에 공표된 C++ 표준에 소급 적용됐어요.

DR 적용 대상 공표된 동작 올바른 동작
CWG 288 C++98 첫 번째 형태에서 피연산자의 정적 타입과 동적 타입을 비교했음 삭제할 객체의 정적 타입과 동적 타입을 비교
CWG 353 C++98 소멸자가 예외를 던지면 할당 해제 함수를 호출할지 불특정이었음 항상 호출
CWG 599 C++98 첫 번째 형태가 함수 포인터를 포함한 어떤 타입의 널 포인터도 받을 수 있었음 객체 타입에 대한 포인터를 제외한 모든 포인터 타입 거부
CWG 1642 C++98 expression이 포인터 lvalue일 수 있었음 허용되지 않음
CWG 2474 C++98 유사하지만 다른 타입의 객체에 대한 포인터 삭제가 정의되지 않은 동작이었음 잘 정의되도록 만듦
CWG 2624 C++98 비할당 operator new[]에서 얻은 포인터를 delete[]에 넘길 수 있었음 금지
CWG 2758 C++98 할당 해제 함수와 소멸자에 대한 접근 제어가 불명확했음 명확히 만듦

더 알아보기 (Learn more)

  • new 표현식delete로 되돌려 줄 객체를 할당하는 반대쪽 작업. (cppreference)
  • 할당 해제 함수 (Deallocation functions)operator delete 계열 함수들의 종류와 선택 규칙. (cppreference)
  • 소멸자 (Destructor) — 객체가 파괴될 때 호출되는 특별 멤버 함수. (cppreference)