new
expression
동적 storage duration 을 가진 객체들을 생성하고 초기화합니다. 즉, 생성된 스코프에 의해 수명이 반드시 제한되지 않는 객체들입니다.
목차 |
구문
::
(선택적)
new
(
type
)
new-initializer
(선택적)
|
(1) | ||||||||
::
(선택적)
new
type
new-initializer
(선택적)
|
(2) | ||||||||
::
(선택적)
new
(
placement-args
)
(
type
)
new-initializer
(선택적)
|
(3) | ||||||||
::
(선택적)
new
(
placement-args
)
type
new-initializer
(선택적)
|
(4) | ||||||||
설명
| type | - | 대상 타입 ID |
| new-initializer | - | 괄호로 둘러싸인 표현식 목록 또는 중괄호로 둘러싸인 초기화자 목록 (C++11부터) |
| placement-args | - | 추가 배치 인수 |
new
표현식은 저장 공간을 할당하려 시도한 후, 할당된 저장 공간에서 단일 무명 객체 또는 무명 객체 배열을 생성하고 초기화하려 시도합니다.
new
표현식은 생성된 객체에 대한 prvalue 포인터를 반환하거나, 객체 배열이 생성된 경우 배열의 첫 번째 요소를 가리키는 포인터를 반환합니다.
구문 (1) 또는 (3) 은 type 에 괄호가 포함된 경우 필요합니다:
new int(*[10])(); // 오류: (new int) (*[10]) ()로 파싱됨 new (int (*[10])()); // 정상: 10개의 함수 포인터 배열을 할당함
또한, type 은 탐욕적으로 파싱됩니다: 선언자의 일부가 될 수 있는 모든 토큰을 포함하도록 취해질 것입니다:
new int + 1; // 정상: (new int) + 1로 파싱되며, new int가 반환한 포인터를 증가시킴 new int * 1; // 오류: (new int*) (1)로 파싱됨
new-initializer 는 다음과 같은 경우 선택 사항이 아닙니다
- type 은 크기를 알 수 없는 배열 입니다,
|
(C++11부터) |
|
(C++17부터) |
double* p = new double[]{1, 2, 3}; // double[3] 타입의 배열을 생성합니다 auto p = new auto('c'); // char 타입의 단일 객체를 생성합니다. p는 char*입니다 auto q = new std::integral auto(1); // OK: q는 int*입니다 auto q = new std::floating_point auto(true) // ERROR: 타입 제약 조건을 만족하지 않습니다 auto r = new std::pair(1, true); // OK: r는 std::pair<int, bool>*입니다 auto r = new std::vector; // ERROR: 요소 타입을 추론할 수 없습니다
동적 배열
만약 type 이 배열 타입인 경우, 첫 번째 차원을 제외한 모든 차원은 양의 정수 상수 표현식 (C++14까지) 변환된 상수 표현식 으로 std::size_t 타입 (C++14부터) 으로 지정되어야 합니다. 하지만 (괄호 없는 구문 (2) 와 (4) 를 사용할 때만) 첫 번째 차원은 정수 타입, 열거형 타입, 또는 정수/열거형 타입으로의 단일 비명시적 변환 함수를 가진 클래스 타입의 표현식 (C++14까지) std::size_t 로 변환 가능한 모든 표현식 (C++14부터) 일 수 있습니다. 이것은 런타임에 크기가 정의된 배열을 직접 생성하는 유일한 방법이며, 이러한 배열들은 일반적으로 동적 배열 이라고 불립니다:
int n = 42; double a[n][5]; // 오류 auto p1 = new double[n][5]; // 정상 auto p2 = new double[5][n]; // 오류: 첫 번째 차원만 비상수일 수 있음 auto p3 = new (double[n][5]); // 오류: 구문 (1)은 동적 배열에 사용할 수 없음
|
첫 번째 차원의 값(필요한 경우 정수형이나 열거형으로 변환된)이 음수이면 동작은 정의되지 않습니다. |
(C++11 이전) |
|
다음 경우들에서 첫 번째 차원을 지정하는 표현식의 값은 유효하지 않습니다:
첫 번째 차원의 값이 이러한 이유들 중 어느 하나로 인해 유효하지 않으면:
|
(C++11 이후) |
첫 번째 차원이 0인 것은 허용되며, 할당 함수가 호출됩니다.
|
만약 new-initializer 가 중괄호로 둘러싸인 초기화 리스트이고, 첫 번째 차원이 잠재적으로 평가되는(potentially evaluated) 표현식이며 핵심 상수 표현식(core constant expression) 이 아닌 경우, 빈 초기화 리스트로부터 배열의 가상 요소를 복사 초기화(copy-initializing) 할 때의 의미론적 제약 조건들이 검사됩니다. |
(C++11부터) |
할당
new 표현식은 적절한 할당 함수 를 호출하여 저장 공간을 할당합니다. type 이 비배열 타입인 경우, 함수의 이름은 operator new 입니다. type 이 배열 타입인 경우, 함수의 이름은 operator new [ ] 입니다.
할당 함수
에서 설명된 바와 같이, C++ 프로그램은 이러한 함수들에 대한 전역 및 클래스별 대체를 제공할 수 있습니다. 만약
new
표현식이 선택적
::
연산자로 시작하는 경우, 즉
::
new
T
또는
::
new
T
[
n
]
와 같은 경우, 클래스별 대체는 무시됩니다(함수는 전역
범위
에서
조회
됩니다). 그렇지 않고
T
가 클래스 타입인 경우, 조회는
T
의 클래스 범위에서 시작됩니다.
할당 함수를 호출할 때,
new
표현식은 요청된 바이트 수를 첫 번째 인자로 전달하며, 이는
std::size_t
타입입니다. 비배열
T
의 경우 이 값은 정확히
sizeof
(
T
)
입니다.
배열 할당은 지정되지 않은 오버헤드를 제공할 수 있으며, 이는 선택된 할당 함수가 표준 비할당 형태가 아닌 한 new 호출마다 달라질 수 있습니다. new 표현식이 반환하는 포인터는 할당 함수가 반환한 포인터에서 해당 값만큼 오프셋됩니다. 많은 구현에서 배열 오버헤드를 사용해 배열 내 객체 개수를 저장하며, 이는 delete [ ] 표현식이 올바른 수의 소멸자를 호출하는 데 사용됩니다. 또한, new 표현식이 char , unsigned char , 또는 std::byte (C++17부터) 의 배열을 할당하는 데 사용되는 경우, 필요시 할당 함수로부터 추가 메모리를 요청할 수 있으며, 이는 이후 할당된 배열에 배치될 요청된 배열 크기보다 크지 않은 모든 타입의 객체에 대한 정확한 정렬을 보장하기 위함입니다.
|
new 표현식은 대체 가능한 할당 함수를 통해 수행되는 할당을 생략하거나 결합할 수 있습니다. 생략의 경우, 할당 함수 호출 없이 컴파일러가 저장 공간을 제공할 수 있습니다(이는 사용되지 않는 new 표현식을 최적화하여 제거하는 것도 허용합니다). 결합의 경우, new 표현식 E1 에 의해 수행된 할당이 다른 new 표현식 E2 를 위한 추가 저장 공간을 제공하도록 확장될 수 있으며, 이는 다음 조건 모두가 참일 때 가능합니다:
1)
E1
에 의해 할당된 객체의 수명이
E2
에 의해 할당된 객체의 수명을 완전히 포함합니다.
2)
E1
과
E2
가 동일한 대체 가능한 전역 할당 함수를 호출할 경우입니다.
3)
예외를 발생시키는 할당 함수의 경우,
E1
과
E2
의 예외가 동일한 핸들러에서 처음으로 포착될 경우입니다.
이 최적화는 new 표현식을 사용할 때만 허용되며, 대체 가능한 할당 함수를 호출하는 다른 방법에는 적용되지 않습니다: delete [ ] new int [ 10 ] ; 는 최적화되어 제거될 수 있지만, operator delete ( operator new ( 10 ) ) ; 는 최적화될 수 없습니다. |
(C++14부터) |
|
상수 표현식 평가 중에는 할당 함수 호출이 항상 생략됩니다. 상수 표현식 내에서는 대체 가능한 전역 할당 함수를 호출하게 될 new 표현식만 평가될 수 있습니다. |
(C++20부터) |
배치 new
만약 placement-args 가 제공되면, 이는 추가 인자로 할당 함수에 전달됩니다. 이러한 할당 함수는 표준 할당 함수 void * operator new ( std:: size_t , void * ) (단순히 두 번째 인자를 변경 없이 반환하는 함수) 이후 "placement new "로 알려져 있습니다. 이것은 할당된 저장 공간에 객체를 생성하는 데 사용됩니다:
// 임의의 블록 범위 내에서... { // 자동 저장 기간을 가지는 정적 할당 스토리지 // 이는 "T" 타입의 모든 객체에 충분히 큰 크기입니다. alignas(T) unsigned char buf[sizeof(T)]; T* tptr = new(buf) T; // "T" 객체를 생성하여, 사전 할당된 스토리지의 // 메모리 주소 "buf"에 직접 배치합니다. tptr->~T(); // 프로그램이 객체의 부수 효과에 의존하는 경우 // **반드시 수동으로** 객체의 소멸자를 호출해야 합니다. } // 이 블록 범위를 벗어나면 "buf"가 자동으로 해제됩니다.
참고: 이 기능은 Allocator 클래스의 멤버 함수들에 의해 캡슐화되어 있습니다.
|
정렬 요구 사항이 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 를 초과하는 객체 또는 그러한 객체들의 배열을 할당할 때, new 표현식은 할당 함수에 대한 두 번째 인수로 정렬 요구 사항( std::align_val_t 로 래핑됨)을 전달합니다(배치 형태의 경우, placement-arg 가 정렬 이후에 세 번째, 네 번째 등의 인수로 나타납니다). 오버로드 해결이 실패할 경우(서명이 다른 클래스별 할당 함수가 정의되어 전역 함수를 가릴 때 발생), 인수 목록에서 정렬을 제외하고 두 번째로 오버로드 해결을 시도합니다. 이를 통해 정렬 인식을 지원하지 않는 클래스별 할당 함수가 전역 정렬 인식 할당 함수보다 우선순위를 가질 수 있습니다. |
(C++17부터) |
new T; // operator new(sizeof(T)) 호출 // (C++17) 또는 operator new(sizeof(T), std::align_val_t(alignof(T)))) new T[5]; // operator new[](sizeof(T)*5 + overhead) 호출 // (C++17) 또는 operator new(sizeof(T)*5+overhead, std::align_val_t(alignof(T)))) new(2,f) T; // operator new(sizeof(T), 2, f) 호출 // (C++17) 또는 operator new(sizeof(T), std::align_val_t(alignof(T)), 2, f)
비-던지기 할당 함수(예: new ( std:: nothrow ) T 에 의해 선택된 함수)가 할당 실패로 인해 null 포인터를 반환하면, new 표현식은 즉시 반환되며 객체 초기화나 할당 해제 함수 호출을 시도하지 않습니다. 만약 null 포인터가 비-할당 배치 new 표현식의 인수로 전달되어, 선택된 표준 비-할당 배치 할당 함수가 null 포인터를 반환하는 경우, 그 동작은 정의되지 않습니다.
초기화
new 표현식에 의해 생성된 객체는 다음 규칙에 따라 초기화됩니다.
만약 type 가 배열 타입이 아닌 경우, 단일 객체가 획득한 메모리 영역에 생성됩니다:
- new-initializer 가 없으면, 객체는 default-initialized 됩니다.
- new-initializer 가 괄호로 둘러싸인 표현식 목록이면, 객체는 direct-initialized 됩니다.
|
(C++11부터) |
만약 type 이 배열 타입인 경우, 객체들의 배열이 초기화됩니다:
- 만약 new-initializer 가 없으면, 각 요소는 default-initialized 됩니다.
-
- 첫 번째 차원이 0인 경우에도, 가상 요소를 기본 초기화하는 의미론적 제약 조건은 여전히 충족되어야 합니다.
- 만약 new-initializer 가 한 쌍의 괄호인 경우, 각 요소는 value-initialized 됩니다.
-
- 첫 번째 차원이 0인 경우에도 가상 요소의 값 초기화에 대한 의미론적 제약 조건은 여전히 충족되어야 합니다.
|
(C++11부터) |
|
(C++20부터) |
초기화 실패
초기화가 예외를 던져서 종료되는 경우(예: 생성자에서), 프로그램은 일치하는 할당 해제 함수를 찾은 후:
- 적절한 할당 해제 함수를 찾을 수 있는 경우, 할당 해제 함수가 호출되어 객체가 생성 중이던 메모리를 해제합니다. 이후 예외는 new 표현식의 컨텍스트에서 계속 전파됩니다.
- 명확하게 일치하는 할당 해제 함수를 찾을 수 없는 경우, 예외를 전파해도 객체의 메모리가 해제되지 않습니다. 이는 호출된 할당 함수가 메모리를 할당하지 않는 경우에만 적합하며, 그렇지 않으면 메모리 누수가 발생할 가능성이 높습니다.
매칭되는 할당 해제 함수의 lookup 범위는 다음과 같이 결정됩니다:
-
만약
new
표현식이
::로 시작하지 않고, 할당된 타입이 클래스 타입T이거나 클래스 타입T의 배열인 경우, 할당 해제 함수의 이름을T의 클래스 범위에서 검색합니다. -
그렇지 않거나,
T의 클래스 범위에서 아무것도 찾지 못한 경우, 할당 해제 함수의 이름은 전역 범위 에서 검색하여 조회됩니다.
비배치 할당 함수의 경우, 일치하는 비할당 함수를 찾기 위해 일반적인 비할당 함수 조회가 사용됩니다( delete-expression 참조).
배치 할당 함수에 대해서는, 해당 해제 함수가 동일한 수의 매개변수를 가져야 하며, 첫 번째 매개변수를 제외한 각 매개변수의 유형이 할당 함수의 해당 매개변수 유형과 동일해야 합니다( 매개변수 변환 이후).
- 조회가 단일 일치하는 할당 해제 함수를 찾으면 해당 함수가 호출됩니다; 그렇지 않으면 할당 해제 함수가 호출되지 않습니다.
- 조회가 비배치 할당 해제 함수를 찾고, 해당 함수가 배치 할당 해제 함수로 간주되었을 때 할당 함수에 대한 일치 항목으로 선택되었을 경우, 프로그램은 잘못된 형식입니다.
어떤 경우에도, 해당 할당 해제 함수(존재한다면)는 삭제되지 않고 (C++11부터) new 표현식이 나타나는 지점에서 접근 가능해야 합니다.
struct S { // 배치 할당 함수: static void* operator new(std::size_t, std::size_t); // 비배치 해제 함수: static void operator delete(void*, std::size_t); }; S* p = new (0) S; // 오류: 비배치 해제 함수가 배치 할당 함수와 일치함 //
할당 해제 함수가 new 표현식에서 (초기화 실패로 인해) 호출되는 경우, 해당 함수에 전달되는 인수들은 다음과 같이 결정됩니다:
- 첫 번째 인수는 할당 함수 호출에서 반환된 값( void * 타입)입니다.
- 다른 인수들(배치 해제 함수에만 해당)은 배치 할당 함수에 전달된 placement-args 입니다.
구현이 할당 함수 호출의 일부로 임시 객체를 도입하거나 어떤 인수의 복사본을 만들 수 있는 경우, 동일한 객체가 할당 및 해제 함수 모두의 호출에 사용되는지는 명시되지 않습니다.
메모리 누수
new 표현식으로 생성된 객체들(동적 저장 기간을 가진 객체들)은 new 표현식이 반환한 포인터가 상응하는 delete-expression 에 사용될 때까지 존재합니다. 포인터의 원래 값을 잃어버리면 객체에 접근할 수 없게 되고 할당 해제할 수 없게 됩니다: 메모리 누수 가 발생합니다.
이는 포인터가 다음 중 하나에 할당된 경우 발생할 수 있습니다:
int* p = new int(7); // 값 7을 가진 동적으로 할당된 int p = nullptr; // 메모리 누수
또는 포인터가 범위를 벗어나는 경우:
void f() { int* p = new int(7); } // 메모리 누수
또는 예외로 인해:
void f() { int* p = new int(7); g(); // 예외를 발생시킬 수 있음 delete p; // 예외가 없으면 정상 동작 } // g()가 예외를 발생시키면 메모리 누수 발생
동적으로 할당된 객체의 관리를 단순화하기 위해, new 표현식의 결과는 종종 스마트 포인터 에 저장됩니다: std::auto_ptr (C++17까지) std::unique_ptr , 또는 std::shared_ptr (C++11부터) . 이러한 포인터들은 위에서 보여진 상황들에서 delete 표현식이 실행되도록 보장합니다.
참고 사항
Itanium C++ ABI 는 생성된 배열의 요소 타입이 사소하게 파괴 가능(trivially destructible)할 경우 배열 할당 오버헤드가 0이어야 한다고 요구합니다. MSVC도 마찬가지입니다.
일부 구현(예: VS 2019 v16.7 이전의 MSVC)에서는 요소 타입이 trivially destructible이 아닌 경우, 비할당 배치 배열 new 에 대해 0이 아닌 배열 할당 오버헤드를 요구하는데, 이는 CWG issue 2382 이후부터 더 이상 표준을 준수하지 않습니다.
할당을 수행하지 않는 배치 배열 new 표현식은 unsigned char 또는 std::byte (C++17부터) 배열을 생성하여 주어진 저장 영역에 객체를 암시적으로 생성 하는 데 사용될 수 있습니다: 이는 배열과 겹치는 객체들의 수명을 종료시키고, 배열 내에서 암시적 수명 타입들의 객체를 암시적으로 생성합니다.
std::vector 1차원 동적 배열에 대해 유사한 기능을 제공합니다.
키워드
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 74 | C++98 | 첫 번째 차원의 값은 정수형이어야 함 | 열거형 허용 |
| CWG 299 | C++98 |
첫 번째 차원의 값은 정수형 또는
열거형이어야 함 |
정수형 또는 열거형으로의 단일 변환 함수를
가진 클래스 타입 허용 |
| CWG 624 | C++98 |
할당된 객체의 크기가 구현 정의 한계를
초과할 때의 동작이 명시되지 않음 |
이 경우 저장 공간을 얻지 못하고
예외가 발생함 |
| CWG 1748 | C++98 |
비할당 배치
new
에서 인자가
null인지 확인해야 함 |
null 인자에 대해 정의되지 않은 동작 |
| CWG 1992 | C++11 |
new
(
std::
nothrow
)
int
[
N
]
가 std::bad_array_new_length 를 발생시킬 수 있음 |
null 포인터를 반환하도록 변경 |
| CWG 2102 | C++98 |
빈 배열을 초기화할 때 기본/값 초기화가
올바른 형식이어야 하는지 불명확했음 |
필수로 지정 |
| CWG 2382 | C++98 |
비할당 배치 배열
new
에서
할당 오버헤드가 필요할 수 있음 |
이러한 할당 오버헤드가 허용되지 않음 |
| CWG 2392 | C++11 |
첫 번째 차원이 잠재적으로 평가되지 않는
경우에도 프로그램이 올바른 형식이 아닐 수 있음 |
이 경우 올바른 형식으로 지정 |
| P1009R2 | C++11 |
new
표현식에서 배열 경계를
추론할 수 없었음 |
추론 허용 |