utility_launder
utility_launder (객체 포인터 재보정)
std::launder는 C++17에서 추가된 유틸리티 함수로, 주어진 포인터가 나타내는 주소에 있는 객체를 가리키는 새로운 포인터를 반환해요. 객체 수명이 끝난 후 같은 저장 공간에 새로 생성된 객체에 접근해야 할 때, 특히 base class 하위 객체와 관련된 상황에서 유용하게 사용할 수 있어요. 이 함수는 컴파일러의 가정을 조정하는 일종의 "비가상화 펜스" 역할을 해요.
출처: cppreference
본문
정의
<new> 헤더에 정의되어 있어요.
<new> 헤더에 정의됨 |
||
|---|---|---|
template < class T > constexpr T * launder ( T * p ) noexcept ; |
(C++17 이후) |
설명
p에 대한 비가상화 펜스예요. p가 나타내는 같은 주소에 있는 객체를 가리키는 포인터를 반환해요. 이때 객체는 원래 *p 객체의 최종 파생 클래스와 다른 최종 파생 클래스를 가진 새로운 base class 하위 객체일 수 있어요.
형식적으로 말하면, 다음 조건이 주어졌을 때:
- 포인터
p가 메모리 바이트의 주소A를 나타내고, - 객체
x가 주소A에 있고, x가 수명 기간 내에 있고,x의 타입이 모든 수준에서 cv-한정자를 무시할 때T와 동일하고,- 결과를 통해 도달할 수 있는 모든 바이트가
p를 통해 도달 가능해야 해요. (바이트는 객체y를 가리키는 포인터를 통해 도달할 수 있는데, 그 바이트가y와 포인터 상호 변환 가능한 객체z의 저장 공간 안에 있거나,z가 요소인 바로 둘러싼 배열 안에 있는 경우를 말해요.)
그러면 std::launder(p)는 객체 x를 가리키는 T* 타입 값을 반환해요. 그렇지 않으면 동작이 정의되지 않아요.
T가 함수 타입이거나 (cv-한정될 수 있는) void라면 프로그램은 ill-formed예요.
std::launder는 인자의 (변환된) 값을 함수 호출 대신 사용할 수 있는 경우에만 핵심 상수 표현식에서 사용할 수 있어요. 즉, std::launder는 상수 평가에서 제한을 완화하지 않아요.
Notes
std::launder는 인자에 아무 효과도 없어요. 반환 값을 객체 접근에 사용해야 해요. 따라서 반환 값을 버리는 것은 항상 오류예요.
std::launder의 일반적인 용도는 다음과 같아요:
- 같은 타입의 기존 객체 저장 공간에 생성된 객체를 가리키는 포인터를 얻는 경우 (예를 들어 두 객체 중 하나가 base class 하위 객체라서 이전 객체에 대한 포인터를 재사용할 수 없는 경우);
- 그 객체를 위한 저장 공간을 제공하는 객체에 대한 포인터로부터 placement new로 생성된 객체를 가리키는 포인터를 얻는 경우.
도달 가능성 제한은 std::launder를 원래 포인터로 접근할 수 없는 바이트에 접근하는 데 사용할 수 없도록 보장해서, 컴파일러의 이스케이프 분석을 방해하지 않아요.
int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); // OK
int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
// Undefined behavior: x2[1] would be reachable through the resulting pointer to x2[0]
// but is not reachable from the source
struct X { int a[10]; } x3, x4[2]; // standard layout; assume no padding
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK
auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0]));
// Undefined behavior: x4[1] would be reachable through the resulting pointer to x4[0].a
// (which is pointer-interconvertible with x4[0]) but is not reachable from the source
struct Y { int a[10]; double y; } x5;
auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0]));
// Undefined behavior: x5.y would be reachable through the resulting pointer to x5.a
// but is not reachable from the source
Example
#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()
{
// Case 1: the new object failed to be transparently replaceable because
// it is a base subobject but the old object is a complete object.
Base base;
int n = base.transmogrify();
// int m = base.transmogrify(); // undefined behavior
int m = std::launder(&base)->transmogrify(); // OK
assert(m + n == 3);
// Case 2: access to a new object whose storage is provided
// by a byte array through a pointer to the array.
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; // Class member access is undefined
// behavior: reinterpret_cast<Y*>(&s)
// has value "pointer to s" and does
// not point to a Y object
const int g = q->z; // OK
const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK
[](...){}(f, g, h); // evokes [[maybe_unused]] effect
}
Defect reports
다음 동작 변경 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었어요.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| LWG 2859 | C++17 | 도달 가능의 정의가 포인터 상호 변환 가능한 객체로부터의 포인터 연산을 고려하지 않았음 | 포함됨 |
| LWG 3495 | C++17 | std::launder가 상수 표현식에서 비활성 멤버를 가리키는 포인터를 역참조 가능하게 만들 수 있었음 |
금지됨 |