Namespaces
Variants

Lifetime

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

모든 객체 참조 수명(lifetime) 을 가지며, 이는 런타임 속성입니다: 모든 객체나 참조에 대해 수명이 시작되는 프로그램 실행 지점이 존재하며, 수명이 종료되는 시점도 존재합니다.

객체의 수명은 다음과 같은 경우에 시작됩니다:

  • 객체가 union 멤버 또는 그 하위 객체인 경우, 해당 union 멤버가 union에서 초기화된 멤버이거나 활성화된 경우에만 수명이 시작됩니다,
  • 객체가 union 객체 내에 중첩된 경우, 포함하는 union 객체가 trivial 특수 멤버 함수에 의해 할당되거나 생성되면 해당 객체의 수명이 시작될 수 있습니다,
  • 배열 객체의 수명은 std::allocator::allocate 에 의해 할당된 경우에도 시작될 수 있습니다.

일부 연산들은 객체를 암시적으로 생성하여 주어진 저장 영역에서 implicit-lifetime 타입 의 객체를 만들고 그 수명을 시작합니다. 암시적으로 생성된 객체의 하위 객체가 implicit-lifetime 타입이 아닌 경우, 그 수명은 암시적으로 시작되지 않습니다.

객체의 수명이 종료되는 경우:

  • 비클래스 타입인 경우, 객체가 파괴됩니다(의사 소멸자 호출을 통해), 또는
  • 클래스 타입인 경우, destructor 호출이 시작되거나,
  • 객체가 점유하는 저장 공간이 해제되거나, 그 내부에 중첩되지 않은 객체에 의해 재사용 되는 경우입니다.

객체의 수명은 해당 저장 공간의 수명과 같거나 그 안에 중첩됩니다. 자세한 내용은 storage duration 을 참조하십시오.

참조 의 수명은 초기화가 완료될 때 시작되며 스칼라 객체인 것처럼 끝납니다.

참고: 참조된 객체의 수명이 참조의 수명보다 먼저 종료될 수 있으며, 이로 인해 dangling references 가 발생할 가능성이 있습니다.

비정적 데이터 멤버와 기본 클래스 하위 객체의 수명은 클래스 초기화 순서 에 따라 시작되고 종료됩니다.

목차

임시 객체 수명

임시 객체는 프르밸류가 글밸류로 사용될 수 있도록 구체화(materialized) 될 때 생성되며, 이는 다음 상황에서 발생합니다 (since C++17) :

(C++11부터)
(C++11부터)
(C++17 이전)

임시 객체의 구체화는 불필요한 임시 객체 생성을 피하기 위해 가능한 한 지연됩니다: 복사 생략 을 참조하십시오.

(C++17부터)


T 타입의 객체가 potentially-evaluated 함수 호출에 전달되거나 반환될 때, T 가 다음 유형 중 하나인 경우 구현체는 함수 매개변수나 결과 객체를 보관하기 위해 임시 객체를 생성할 수 있습니다:

(since C++26)
  • 다음 모든 조건을 만족하는 클래스 타입:
    • T 가 적어도 하나의 eligible copy 또는 move 생성자를 가짐.
    • T 의 각 eligible copy/move 생성자가 trivial함.
    • T destructor 가 trivial하거나 deleted임.

임시 객체는 각각 함수 인수나 반환값으로부터 생성되며, 함수의 매개변수나 반환 객체는 eligible trivial 생성자를 사용하여 임시 객체를 복사하는 것처럼 초기화됩니다 (해당 생성자가 접근 불가능하거나 오버로드 해결에 의해 객체의 복사나 이동을 수행하기 위해 선택되지 않을 경우에도).

(until C++26)

임시 객체는 다음과 같이 생성됩니다:

  • 첫 번째 임시 객체는 각각 함수 인수나 반환값으로부터 생성됩니다.
  • 각 후속 임시 객체는 이전 객체로부터 초기화됩니다: T 가 scalar type인 경우 direct-initialization 처럼, 그렇지 않은 경우 eligible trivial 생성자를 사용하여.
  • 함수 매개변수나 반환 객체는 최종 임시 객체로부터 초기화됩니다: T 가 scalar type인 경우 direct initialization처럼, 그렇지 않은 경우 eligible trivial 생성자를 사용하여.

모든 경우에, 해당 생성자가 접근 불가능하거나 오버로드 해결에 의해 객체의 복사나 이동을 수행하기 위해 선택되지 않을 경우에도 eligible 생성자가 사용됩니다.

(since C++26)

이 허용은 객체가 레지스터를 통해 함수에 전달되거나 함수로부터 반환될 수 있도록 하기 위해 부여됩니다.

(since C++17)

모든 임시 객체는 그것들이 생성된 지점을 (어휘적으로) 포함하는 완전 표현식(full-expression) 을 평가하는 마지막 단계에서 파괴되며, 여러 임시 객체가 생성된 경우 생성 순서의 역순으로 파괴됩니다. 이는 해당 평가가 예외를 발생시키며 종료되는 경우에도 적용됩니다.

다음은 그에 대한 예외 사항입니다:

  • 임시 객체의 수명은 참조에 바인딩함으로써 연장될 수 있습니다. 자세한 내용은 참조 초기화 를 참조하십시오.
  • 배열의 요소를 초기화하거나 복사하는 데 사용되는 기본 또는 복사 생성자의 기본 인수를 평가할 때 생성된 임시 객체의 수명은 배열의 다음 요소 초기화가 시작되기 전에 종료됩니다.
  • 고유한 이름을 가진 변수의 초기화자에 의해 도입된 구조화된 바인딩 선언에서 생성된 임시 객체의 수명은 구조화된 바인딩 선언의 끝까지 확장됩니다.
(C++17부터)
  • 범위- for 문의 범위-초기화자 에서 생성된 임시 객체의 수명은, 그렇지 않으면 범위-초기화자 끝에서 소멸되었을 것이지만, 루프 본문의 끝까지 확장됩니다.
(C++23부터)

스토리지 재사용

객체가 trivially-destructible 인 경우(프로그램의 올바른 동작이 소멸자에 의존할 수 있으므로 주의해야 함), 해당 객체의 수명을 종료하기 위해 소멸자를 호출할 필요가 없습니다. 그러나 non-trivially destructible 객체인 변수의 수명을 명시적으로 종료하는 경우, 소멸자가 암시적으로 호출되기 전에(예: 자동 객체의 경우 범위를 벗어나거나 예외 발생 시, thread-local 객체의 경우 스레드 종료 시, (C++11부터) static 객체의 경우 프로그램 종료 시) 동일한 타입의 새 객체를 해당 위치에 생성해야 합니다(예: placement new 를 통해). 그렇지 않으면 동작이 정의되지 않습니다.

class T {}; // trivial
struct B
{
    ~B() {} // non-trivial
};
void x()
{
    long long n; // automatic, trivial
    new (&n) double(3.14); // 다른 타입으로 재사용 가능
} // 정상
void h()
{
    B b; // 자동 비-자명 소멸 가능
    b.~B(); // 수명 종료 (부수 효과가 없으므로 필수 아님)
    new (&b) T; // 잘못된 타입: 소멸자가 호출되기 전까지는 정상
} // 소멸자가 호출됨: 정의되지 않은 동작

정적 저장 기간을 갖는 const 완전 객체가 차지했거나 차지하고 있는 저장 공간을 재사용하는 것은 미정의 동작입니다. , 스레드 지역, (C++11부터) 또는 자동 저장 기간을 갖는 객체들은 읽기 전용 메모리에 저장될 수 있기 때문입니다:

struct B
{
    B(); // non-trivial
    ~B(); // non-trivial
};
const B b; // const static
void h()
{
    b.~B(); // b의 수명 종료
    new (const_cast<B*>(&b)) const B; // 정의되지 않은 동작: const 객체의 재사용 시도
}

new expression 을 평가할 때, 저장 공간은 allocation function 에서 반환된 후, 그러나 new 표현식의 initializer 평가가 이루어지기 전에 재사용된 것으로 간주됩니다:

struct S
{
    int m;
};
void f()
{
    S x{1};
    new(&x) S(x.m); // 정의되지 않은 동작: 저장소가 재사용됨
}

만약 새로운 객체가 다른 객체가 점유하고 있던 주소에 생성되면, 모든 포인터, 참조, 그리고 원본 객체의 이름은 자동으로 새로운 객체를 참조하게 되며, 새로운 객체의 수명이 시작되면 새로운 객체를 조작하는 데 사용될 수 있습니다. 그러나 이는 원본 객체가 새로운 객체에 의해 투명하게 대체 가능한 경우에만 해당됩니다.

다음 모든 조건이 충족되면, 객체 x 는 객체 y 투명하게 대체 가능 합니다:

  • y 의 저장 공간이 x 가 차지했던 저장 위치와 정확히 겹칩니다.
  • y x 와 동일한 타입입니다(최상위 cv-한정자는 무시).
  • x 는 완전한 const 객체가 아닙니다.
  • x y 모두 베이스 클래스 서브오브젝트 , 또는 [[ no_unique_address ]] 로 선언된 멤버 서브오브젝트가 아닙니다 (C++20부터) .
  • 다음 조건 중 하나가 충족됩니다:
  • x y 가 모두 완전한 객체(complete objects)입니다.
  • x y 는 각각 객체 ox oy 의 직접 하위 객체(direct subobjects)이며, ox oy 에 의해 투명하게 대체 가능(transparently replaceable)합니다.
struct C
{
    int i;
    void f();
    const C& operator=(const C&);
};
const C& C::operator=(const C& other)
{
    if (this != &other)
    {
        this->~C();          // *this의 수명이 종료됨
        new (this) C(other); // C 타입의 새로운 객체가 생성됨
        f();                 // 잘 정의됨
    }
    return *this;
}
C c1;
C c2;
c1 = c2; // 잘 정의됨
c1.f();  // 잘 정의됨; c1은 C 타입의 새로운 객체를 참조함

위에 나열된 조건들이 충족되지 않더라도, 포인터 최적화 장벽 std::launder 를 적용하여 새로운 객체에 대한 유효한 포인터를 얻을 수 있습니다:

struct A
{ 
    virtual int transmogrify();
};
struct B : A
{
    int transmogrify() override { ::new(this) A; return 2; }
};
inline int A::transmogrify() { ::new(this) B; return 1; }
void test()
{
    A i;
    int n = i.transmogrify();
    // int m = i.transmogrify(); // undefined behavior:
    // the new A object is a base subobject, while the old one is a complete object
    int m = std::launder(&i)->transmogrify(); // OK
    assert(m + n == 3);
}
(C++17부터)

마찬가지로, 클래스 멤버나 배열 요소의 저장 공간에 객체가 생성된 경우, 생성된 객체는 다음과 같은 경우에만 원본 객체의 포함 객체의 하위 객체(멤버 또는 요소)입니다:

  • 포함 객체의 수명이 시작되었고 종료되지 않음
  • 새 객체의 저장 공간이 원본 객체의 저장 공간과 정확히 겹침
  • 새 객체가 원본 객체와 동일한 타입임 (cv-qualification 무시)

그렇지 않으면, 원래 하위 객체의 이름으로 새 객체에 접근하기 위해서는 std::launder 가 필요합니다:

(C++17부터)

스토리지 제공

특별한 경우로, 객체는 unsigned char 또는 std::byte (C++17부터) 배열 내에서 생성될 수 있습니다 (이 경우 배열이 객체에 대해 저장소를 제공한다 고 말합니다) 만약

  • 배열의 수명이 시작되었고 종료되지 않았음
  • 새 객체를 위한 저장 공간이 배열 내에 완전히 포함됨
  • 이러한 제약 조건을 만족하는 배열 객체가 해당 배열 내에 중첩되어 존재하지 않음

배열의 해당 부분이 이전에 다른 객체에 저장 공간을 제공했다면, 해당 객체의 수명은 저장 공간이 재사용되었기 때문에 종료됩니다. 그러나 배열 자체의 수명은 종료되지 않습니다(해당 저장 공간이 재사용된 것으로 간주되지 않음).

template<typename... T>
struct AlignedUnion
{
    alignas(T...) unsigned char data[max(sizeof(T)...)];
};
int f()
{
    AlignedUnion<int, char> au;
    int *p = new (au.data) int;     // OK, au.data가 스토리지를 제공함
    char *c = new (au.data) char(); // OK, *p의 수명을 종료시킴
    char *d = new (au.data + 1) char();
    return *c + *d; // OK
}

수명 범위 외 접근

객체의 수명이 시작되기 전이지만 해당 객체가 차지할 저장 공간이 할당된 후, 또는 객체의 수명이 종료된 후 해당 객체가 차지하던 저장 공간이 재사용되거나 해제되기 전까지, 해당 객체를 식별하는 glvalue 표현식의 다음과 같은 사용 방식은 객체가 생성 중이거나 소멸 중인 경우(별도의 규칙 집합이 적용됨)를 제외하고 정의되지 않은 동작입니다:

  1. 객체에 접근합니다.
  2. 비정적 데이터 멤버에 접근하거나 비정적 멤버 함수를 호출합니다.
  3. 가상 기본 클래스 서브객체에 대한 참조를 바인딩합니다.
  4. dynamic_cast 또는 typeid 표현식입니다.

위 규칙들은 포인터에도 동일하게 적용됩니다(가상 베이스에 대한 참조 바인딩은 가상 베이스에 대한 포인터로의 암시적 변환으로 대체됨). 여기에 두 가지 추가 규칙이 있습니다:

  1. static_cast 객체가 없는 저장 공간에 대한 포인터의 변환은 (cv 한정자가 있을 수 있는) void * 로 캐스팅할 때만 허용됩니다.
  2. 객체가 없는 저장 공간에 대한 포인터를 (cv 한정자가 있을 수 있는) void * 로 캐스팅한 경우, (cv 한정자가 있을 수 있는) char 포인터, (cv 한정자가 있을 수 있는) unsigned char 포인터 , 또는 (cv 한정자가 있을 수 있는) std::byte 포인터로만 (C++17부터) static_cast 될 수 있습니다.

생성 및 소멸 과정 중에는 일반적으로 비정적 멤버 함수를 호출하고, 비정적 데이터 멤버에 접근하며, typeid dynamic_cast 를 사용하는 것이 허용됩니다. 그러나 수명이 아직 시작되지 않았거나(생성 중) 이미 종료되었기 때문에(소멸 중) 특정 연산만 허용됩니다. 한 가지 제한 사항에 대해서는 생성 및 소멸 중 가상 함수 호출 을 참조하십시오.

참고 사항

CWG 이슈 2256 이 해결되기 전까지, 수명 종료 규칙은 비클래스 객체(저장 기간의 종료)와 클래스 객체(생성의 역순) 간에 다릅니다:

struct A
{
    int* p;
    ~A() { std::cout << *p; } // CWG2256 이후 미정의 동작: n이 a보다 오래 살지 않음
                              // CWG2256 이전까지는 올바른 정의: 123 출력
};
void f()
{
    A a;
    int n = 123; // n이 a보다 오래 살지 않는다면 최적화되어 제거될 수 있음 (dead store)
    a.p = &n;
}

RU007 가 해결되기 전까지, const 한정 타입 또는 참조 타입의 비정적 멤버는 이를 포함하는 객체가 투명하게 대체 가능하는 것을 방지하며, 이로 인해 std::vector std::deque 구현이 어려워집니다:

struct X { const int n; };
union U { X x; float f; };
void tong()
{
    U u = { {1} };
    u.f = 5.f;                          // OK: 'u'의 새로운 하위 객체 생성
    X *p = new (&u.x) X {2};            // OK: 'u'의 새로운 하위 객체 생성
    assert(p->n == 2);                  // OK
    assert(u.x.n == 2);                 // RU007까지 미정의 동작:
                                        // 'u.x'가 새로운 하위 객체를 지칭하지 않음
    assert(*std::launder(&u.x.n) == 2); // RU007까지도 OK
}

결함 보고서

다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 119 C++98 사소하지 않은 생성자를 가진 클래스 타입 객체는
생성자 호출이 완료된 경우에만 수명이 시작될 수 있음
다른 초기화에서도
수명이 시작됨
CWG 201 C++98 기본 생성자의 기본 인수에 있는 임시 객체의 수명은
배열 초기화가 완료될 때 종료되어야 했음
다음 요소 초기화 전에
수명이 종료됨
( CWG 이슈 124 도 해결)
CWG 274 C++98 수명이 끝난 객체를 지정하는 lvalue는 변환이 최종적으로
cv-unqualified char & 또는 unsigned char & 인 경우에만
static_cast의 피연산자로 사용될 수 있었음
cv-qualified char &
unsigned char &
도 허용됨
CWG 597 C++98 다음 동작들이 정의되지 않았음:
1. 수명이 끝난 객체에 대한 포인터가 암시적으로
비가상 기본 클래스에 대한 포인터로 변환됨
2. 수명이 끝난 객체를 참조하는 lvalue가
비가상 기본 클래스에 대한 참조에 바인딩됨
3. 수명이 끝난 객체를 참조하는 lvalue가
static_cast 의 피연산자로 사용됨 (일부 예외 제외)
정의된 동작으로 만듦
CWG 2012 C++98 참조의 수명이 저장 기간과 일치하도록 지정되어,
extern 참조가 초기화 실행 전에 활성 상태여야 했음
초기화 시점에
수명이 시작됨
CWG 2107 C++98 CWG 이슈 124 의 해결책이 복사 생성자에 적용되지 않았음 적용됨
CWG 2256 C++98 사소하게 파괴 가능한 객체의 수명이 다른 객체와 일관되지 않았음 일관되게 만듦
CWG 2470 C++98 둘 이상의 배열이 동일한 객체에 대한 저장소를 제공할 수 있었음 하나만 제공함
CWG 2489 C++98 char [ ] 는 저장소를 제공할 수 없지만 객체가
해당 저장소 내에서 암시적으로 생성될 수 있었음
객체는 char [ ] 의 저장소 내에서
암시적으로 생성될 수 없음
CWG 2527 C++98 저장소 재사용으로 인해 소멸자가 호출되지 않고
프로그램이 그 부수 효과에 의존하는 경우 동작이 정의되지 않았음
이 경우 동작이
정의됨
CWG 2721 C++98 placement new 에 대한 저장소 재사용의 정확한 시점이 불명확했음 명확하게 만듦
CWG 2849 C++23 함수 매개변수 객체가 범위 기반 for 루프 임시 객체
수명 연장을 위한 임시 객체로 간주되었음
임시 객체로
간주되지 않음
CWG 2854 C++98 예외 객체가 임시 객체였음 임시 객체가
아님
CWG 2867 C++17 구조적 바인딩 선언에서 생성된 임시 객체의 수명이
연장되지 않았음
선언 끝까지
연장됨
P0137R1 C++98 unsigned char 배열에서 객체를 생성하면 해당 저장소가 재사용되었음 저장소가 재사용되지 않음
P0593R6 C++98 의사 소멸자 호출이 효과가 없었음 객체를 파괴함
P1971R0 C++98 const-qualified 타입 또는 참조 타입의 비정적 데이터 멤버가
포함 객체의 투명한 대체 가능성을 방해했음
제한이 제거됨
P2103R0 C++98 투명한 대체 가능성이 원래 구조를 유지할 것을 요구하지 않았음 요구함

참고문헌

  • C++23 표준(ISO/IEC 14882:2024):
  • 6.7.3 객체 수명 [basic.life]
  • 11.9.5 생성 및 소멸 [class.cdtor]
  • C++20 표준(ISO/IEC 14882:2020):
  • 6.7.3 객체 수명 [basic.life]
  • 11.10.4 생성 및 소멸 [class.cdtor]
  • C++17 표준(ISO/IEC 14882:2017):
  • 6.8 객체 수명 [basic.life]
  • 15.7 생성 및 소멸 [class.cdtor]
  • C++14 표준(ISO/IEC 14882:2014):
  • 3 객체 수명 [basic.life]
  • 12.7 생성 및 소멸 [class.cdtor]
  • C++11 표준(ISO/IEC 14882:2011):
  • 3.8 객체 수명 [basic.life]
  • 12.7 생성 및 소멸 [class.cdtor]
  • C++03 표준(ISO/IEC 14882:2003):
  • 3.8 객체 수명 [basic.life]
  • 12.7 생성 및 소멸 [class.cdtor]
  • C++98 표준(ISO/IEC 14882:1998):
  • 3.8 객체 수명 [basic.life]
  • 12.7 생성 및 소멸 [class.cdtor]

참고 항목

C documentation for Lifetime