25. unique_ptr + 불완전 타입(Pimpl)에서 소멸자를 헤더에 기본으로 두면 안 되는 이유

난이도 상 해설 보기 →

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

결함 코드 · C++
// 문제 25 — unique_ptr + 불완전 타입(incomplete type)과 Pimpl 소멸자
//
// 아래는 Pimpl(Pointer to Implementation) 관용구로 작성된 게임 서버의
// PlayerSession 클래스다. 헤더/구현을 한 파일에 합쳐 두었지만, 실제로는
// PlayerSession.h / PlayerSession.cpp로 분리되어 있다고 가정하고 주석으로
// 그 경계를 표시했다.
//
// (A) 헤더 부분에서 소멸자를 "= default"로 선언(정의는 아직 안 함)했다.
// (B) Impl의 완전한 정의는 .cpp 파트에만 있다.
//
// 과제:
// 1. 아래 코드를 그대로 하나의 번역 단위(.cpp)로 컴파일하면 성공한다.
//    그런데 만약 (A)의 "~PlayerSession() = default;" 줄을 클래스 정의부(헤더 파트)
//    안으로 그대로 옮겨서 그 자리에서 "= default"로 정의해버리면(즉, .cpp 파트의
//    "PlayerSession::~PlayerSession() = default;" 줄을 지우고 헤더에서 바로
//    "~PlayerSession() = default;" 로 쓰면) 무슨 일이 벌어지는가?
//    컴파일 에러가 나는지, 난다면 어떤 메시지·어느 시점(헤더를 include하는 다른
//    번역 단위? 이 파일 자체?)에 나는지 예측하라.
// 2. 왜 unique_ptr<Impl> 을 멤버로 갖는 클래스는 소멸자(및 이동 연산자)를
//    "헤더에서 선언만 하고 .cpp에서 정의"해야 하는지, unique_ptr의 삭제자가
//    인스턴스화되는 시점과 관련지어 설명하라.
// 3. 만약 Impl을 shared_ptr<Impl>로 바꾼다면 1번과 같은 문제가 여전히 발생하는지,
//    발생하지 않는다면 unique_ptr과 shared_ptr의 삭제자 처리 방식 차이로 설명하라.

#include <memory>
#include <string>
#include <vector>

// ===== PlayerSession.h (헤더 파트) =====
class PlayerSession {
public:
    PlayerSession();
    ~PlayerSession(); // .cpp에서 정의됨 (아래 참고)

    PlayerSession(PlayerSession&&) noexcept;
    PlayerSession& operator=(PlayerSession&&) noexcept;

    PlayerSession(const PlayerSession&) = delete;
    PlayerSession& operator=(const PlayerSession&) = delete;

    void OnRecv(const std::string& data);

private:
    struct Impl; // 불완전 타입 (전방 선언만)
    std::unique_ptr<Impl> impl_;
};

// ===== PlayerSession.cpp (구현 파트) =====
struct PlayerSession::Impl {
    std::vector<std::string> pendingMessages;
    int sessionId = 0;

    void HandleRecv(const std::string& data) {
        pendingMessages.push_back(data);
    }
};

PlayerSession::PlayerSession() : impl_(std::make_unique<Impl>()) {}

// (A) 소멸자는 여기, Impl이 완전한 타입으로 보이는 지점에서 정의된다.
PlayerSession::~PlayerSession() = default;

PlayerSession::PlayerSession(PlayerSession&&) noexcept = default;
PlayerSession& PlayerSession::operator=(PlayerSession&&) noexcept = default;

void PlayerSession::OnRecv(const std::string& data) {
    impl_->HandleRecv(data);
}

int main() {
    PlayerSession session;
    session.OnRecv("hello");
    return 0;
}
내 리뷰 · C++
내 답안 · 자동 저장

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