29. placement new와 수동 소멸자 호출(객체 라이프타임 관리)

난이도 최상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C++
// ============================================================================
// [언어 내부동작 문제] C++ - placement new와 수동 소멸자 호출(객체 라이프타임 관리)
// ----------------------------------------------------------------------------
// 주제: "메모리 할당"과 "객체 생성"은 왜 별개의 단계인가?
//   - 게임서버에서 흔한 패턴: 매 틱(tick)마다 짧게 사는 이벤트/패킷 객체를
//     new/delete 하면 힙 할당 오버헤드가 크다. 그래서 큰 버퍼를 한 번만 할당해두고
//     그 위에서 객체를 반복적으로 생성/파괴하는 "오브젝트 풀"을 직접 구현하곤 한다.
//
// 과제:
//   1) 아래 main 의 출력을 예측하라. 특히 두 번째 슬롯 재사용 시 생성자가 다시
//      호출되는지, 그리고 Pool 소멸 시 몇 개의 소멸자가 호출되는지 예측하라.
//   2) `::operator new(sizeof(Session))` 와 `new (ptr) Session(...)` (placement new)
//      가 각각 어떤 일을 하는지, 왜 둘을 분리해야 오브젝트 풀을 만들 수 있는지
//      설명하라.
//   3) `p->~Session()` 처럼 소멸자를 명시적으로 호출하는 것과 `delete p` 의 차이를
//      설명하라(메모리 해제 여부, 이후 그 메모리를 재사용하는 것이 안전한 이유).
//   4) 이 패턴에서 흔히 발생하는 실수(이중 소멸, 소멸 안 한 채 메모리 재사용,
//      정렬(alignment) 무시)를 설명하라.
//   (먼저 직접 예측·설명을 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

#include <cstdio>
#include <cstddef>
#include <new>

class Session {
public:
    explicit Session(int id) : id_(id) {
        std::printf("Session(%d) 생성\n", id_);
    }
    ~Session() {
        std::printf("Session(%d) 소멸\n", id_);
    }
    void Greet() const {
        std::printf("Session(%d) 인사\n", id_);
    }
private:
    int id_;
};

// 슬롯 2개짜리 아주 단순한 고정 크기 오브젝트 풀
class SessionSlot {
public:
    SessionSlot() : storage_(), alive_(false) {}

    Session* Acquire(int id) {
        // (A) 이미 살아있는 객체가 있다면?
        Session* p = reinterpret_cast<Session*>(&storage_);
        p = new (p) Session(id);   // placement new: 이 자리에 생성
        alive_ = true;
        return p;
    }

    void Release() {
        if (alive_) {
            Session* p = reinterpret_cast<Session*>(&storage_);
            p->~Session();         // (B) 명시적 소멸자 호출
            alive_ = false;
        }
    }

    ~SessionSlot() {
        Release();
    }

private:
    alignas(Session) unsigned char storage_[sizeof(Session)];
    bool alive_;
};

int main() {
    SessionSlot slot;

    Session* s1 = slot.Acquire(1);
    s1->Greet();

    slot.Release();

    // 같은 메모리 위치를 재사용해 새 객체를 생성
    Session* s2 = slot.Acquire(2);
    s2->Greet();

    std::printf("main 끝, slot 소멸 예정\n");
    return 0;
}
내 리뷰 · C++
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.