Namespaces
Variants

std:: launder

From cppreference.net
Utilities library
Memory management library
( exposition only* )
Allocators
Uninitialized memory algorithms
Constrained uninitialized memory algorithms
Memory resources
Uninitialized storage (until C++20)
( until C++20* )
( until C++20* )
( until C++20* )

Garbage collector support (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
헤더에 정의됨 <new>
template < class T >
constexpr T * launder ( T * p ) noexcept ;
(C++17부터)

p 에 대한 디버추얼라이제이션 펜스. p 가 나타내는 객체와 동일한 주소를 가지면서, 객체가 원본 * p 객체와 가장 파생된 클래스가 다른 새로운 기본 클래스 서브오브젝트일 수 있는 객체에 대한 포인터를 반환합니다.

공식적으로, 주어진

  • 포인터 p 가 메모리 내 바이트의 주소 A 를 나타냄
  • 객체 x 가 주소 A 에 위치함
  • x 가 자신의 수명 범위 내에 있음
  • x 의 타입이 모든 수준에서 cv-qualifier를 무시할 때 T 와 동일함
  • 결과를 통해 접근 가능한 모든 바이트가 p를 통해 접근 가능해야 함 (바이트는 객체 y 를 가리키는 포인터를 통해 접근 가능하며, 이러한 바이트들은 객체 z 포인터 상호 변환 가능 하거나 z 가 요소인 바로 바깥 배열 내에 위치하는 경우 해당 바이트에 접근 가능함).

그러면 std :: launder ( p ) 는 객체 x 를 가리키는 T* 타입의 값을 반환합니다. 그렇지 않으면 동작은 정의되지 않습니다.

프로그램은 T 가 함수 타입이거나 (cv 한정자가 있을 수 있는) void 인 경우 형식이 잘못되었습니다.

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 가 상수 표현식에서 비활성 멤버에 대한 포인터를
역참조 가능하게 만들 수 있음
금지됨