std::launder
| 헤더 <new>
|
||
template< class T >
constexpr T* launder( T* p ) noexcept;
|
(C++17부터)에서 정의됨 | |
에 대한 비가상화 펜스. p가 나타내는 동일한 주소의 객체를 가리키는 포인터를 반환하며, 객체는 최종 파생 클래스가 원본 p 객체와 다른 새로운 기본 클래스 하위 객체일 수 있습니다.
*p공식적으로, 다음이 주어졌을 때
포인터
- 는 메모리 내 바이트의 주소
p를 나타냅니다.A객체 - 는 주소
x에 위치하며,A x는 의- 수명
x내에 있습니다.T의 타입은 모든 수준의 cv-한정자를 무시했을 때 - 와 동일합니다.
y결과를 통해 접근 가능한 모든 바이트는 p를 통해 접근 가능합니다 (바이트는 객체z를 가리키는 포인터를 통해 접근 가능한 경우, 해당 바이트가 와 포인터 상호 변환 가능y한 객체z의 저장소 내에 있거나,
가 요소인 바로 둘러싼 배열 내에 있는 경우).std::launder(p)그러면 T*는 객체 x를 가리키는
타입의 값을 반환합니다. 그렇지 않으면, 동작은 정의되지 않습니다.
T가 함수 타입이거나 (cv-한정될 수 있는) void인 경우, 프로그램은 ill-formed입니다.
std::launder는 핵심 상수 표현식에서 사용될 수 있습니다. 단, 해당 인수의 (변환된) 값을 함수 호출 대신 사용할 수 있는 경우에만 가능합니다. 즉, std::launder는 상수 평가에서 제한을 완화하지 않습니다.
참고 사항
std::launder
는 인자에 아무런 영향을 미치지 않습니다. 해당 객체에 접근하기 위해서는 반환값을 사용해야 합니다. 따라서 반환값을 버리는 것은 항상 오류입니다.
std::launder
의 일반적인 사용 사례는 다음과 같습니다:
- 동일한 타입의 기존 객체 저장소에 생성된 객체에 대한 포인터를 획득하는 경우, 이때 이전 객체에 대한 포인터는 재사용될 수 없음 (예: 두 객체 중 하나가 기본 클래스 하위 객체인 경우);
-
객체에 저장소를 제공하는 객체의 포인터로부터 placement
new를 통해 생성된 객체에 대한 포인터를 획득하는 경우.
도달 가능성
제한은
std::launder
가 원본 포인터를 통해 접근할 수 없는 바이트에 접근하는 데 사용될 수 없도록 보장하여, 컴파일러의 탈출 분석을 방해하지 않게 합니다.
int x[10]; auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); // 정상 int x2[2][10]; auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0])); // 정의되지 않은 동작: x2[1]이 결과 포인터를 통해 x2[0]에 도달 가능하지만 // 소스에서는 도달할 수 없음 struct X { int a[10]; } x3, x4[2]; // 표준 레이아웃; 패딩이 없다고 가정 auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // 정상 auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0])); // 정의되지 않은 동작: x4[1]이 결과 포인터를 통해 x4[0].a에 도달 가능하지만 // (이는 x4[0]과 포인터 상호 변환 가능) 소스에서는 도달할 수 없음 struct Y { int a[10]; double y; } x5; auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0])); // 정의되지 않은 동작: x5.y가 결과 포인터를 통해 x5.a에 도달 가능하지만 // 소스에서는 도달할 수 없음
예제
#include <cassert> #include <cstddef> #include <new> struct Base { virtual int transmogrify(); }; struct Derived : Base { int transmogrify() override { new(this) Base; return 2; } }; int Base::transmogrify() { new(this) Derived; return 1; } static_assert(sizeof(Derived) == sizeof(Base)); int main() { // 사례 1: 새로운 객체가 기본 하위 객체이지만 이전 객체가 완전한 객체이기 때문에 // 투명하게 대체 가능하지 않음 Base base; int n = base.transmogrify(); // int m = base.transmogrify(); // 정의되지 않은 동작 int m = std::launder(&base)->transmogrify(); // OK assert(m + n == 3); // 사례 2: 바이트 배열을 통해 제공된 저장 공간을 가진 새 객체에 // 배열에 대한 포인터를 통해 접근 struct Y { int z; }; alignas(Y) std::byte s[sizeof(Y)]; Y* q = new(&s) Y{2}; const int f = reinterpret_cast<Y*>(&s)->z; // 클래스 멤버 접근은 정의되지 않은 동작: // reinterpret_cast<Y*>(&s)는 // "s를 가리키는 포인터" 값을 가지며 // Y 객체를 가리키지 않음 const int g = q->z; // OK const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK [](...){}(f, g, h); // [[maybe_unused]] 효과 유발 }
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| LWG 2859 | C++17 |
도달 가능(reachable)
의 정의가 포인터-상호 변환 가능 객체로부터의
포인터 연산을 고려하지 않음 |
포함됨 |
| LWG 3495 | C++17 |
std::launder
가 상수 표현식에서 비활성 멤버에 대한 포인터를
역참조 가능하게 만들 수 있음 |
금지됨 |