27. Rule of Three/Five/Zero: 사용자 정의 소멸자/복사가 이동 생성자에 미치는 영향

난이도 중 해설 보기 →

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

결함 코드 · C++
// 문제 27 — Rule of Three/Five/Zero: 사용자 정의 소멸자/복사생성자가
//           이동 생성자의 암시적 생성에 미치는 영향
//
// 아래는 게임 서버에서 인벤토리 아이템 목록을 담는 세 가지 버전의 클래스다.
// 셋 다 겉보기엔 비슷하지만, 특수 멤버 함수를 선언한 방식이 다르다.
//
// (A) ItemBagLoud   : 사용자 정의 소멸자만 선언(로그를 남기기 위해).
// (B) ItemBagCopyOnly : 사용자 정의 복사 생성자 + 복사 대입 연산자만 선언
//                       (소멸자·이동은 선언하지 않음).
// (C) ItemBagExplicit : 소멸자를 선언하되, 이동 생성자/이동 대입 연산자를
//                       "= default"로 명시 선언.
//
// 세 클래스 모두 std::vector<ItemBagX>에 push_back을 반복해 재할당이 여러 번
// 일어나게 만든 뒤, 각 클래스의 "복사 생성자 호출 횟수"와 "이동 생성자 호출
// 횟수"를 카운터로 센다.
//
// 과제:
// 1. ItemBagLoud, ItemBagCopyOnly, ItemBagExplicit 각각에 대해 컴파일러가
//    이동 생성자를 암시적으로 생성하는지(생성한다면 그것이 실제로 호출되는지),
//    아니면 이동이 필요한 자리에서 복사로 폴백(fallback)되는지 예측하라.
// 2. std::vector<T>::push_back으로 인한 재할당 시 원소들이 이동되는지 복사되는지는
//    이동 생성자의 존재 여부뿐 아니라 그 이동 생성자가 noexcept인지에도 좌우된다.
//    이 세 클래스에 대해 vector 재할당 시 실제로 어느 연산(복사/이동)이 몇 번
//    호출되는지 카운터 값을 예측하라.
// 3. "사용자 정의 소멸자가 하나라도 있으면 이동 생성자가 암시적으로 삭제/미생성된다"는
//    규칙이 정확히 어떤 조건에서 적용되는지, ItemBagExplicit처럼 이동을
//    "= default"로 명시하면 왜 다시 정상적으로 이동이 일어나는지 설명하라.
//
// 아래 코드는 그대로 컴파일·실행 가능하다 (g++ -std=c++17).

#include <iostream>
#include <vector>
#include <string>
#include <type_traits>

// ---------- (A) 소멸자만 사용자 정의 ----------
struct ItemBagLoud {
    std::string name;
    static inline int copyCount = 0;
    static inline int moveCount = 0;

    explicit ItemBagLoud(std::string n) : name(std::move(n)) {}

    ~ItemBagLoud() {
        // 소멸 로그(실제 학습 목적상 카운트는 세지 않음)
    }

    ItemBagLoud(const ItemBagLoud& other) : name(other.name) {
        ++copyCount;
    }
    ItemBagLoud& operator=(const ItemBagLoud& other) {
        name = other.name;
        ++copyCount;
        return *this;
    }
    // 이동 생성자/이동 대입 연산자는 선언하지 않음.
};

// ---------- (B) 복사 생성자/복사 대입만 사용자 정의 ----------
struct ItemBagCopyOnly {
    std::string name;
    static inline int copyCount = 0;
    static inline int moveCount = 0;

    explicit ItemBagCopyOnly(std::string n) : name(std::move(n)) {}

    ItemBagCopyOnly(const ItemBagCopyOnly& other) : name(other.name) {
        ++copyCount;
    }
    ItemBagCopyOnly& operator=(const ItemBagCopyOnly& other) {
        name = other.name;
        ++copyCount;
        return *this;
    }
    // 소멸자, 이동 생성자/이동 대입 연산자 모두 선언하지 않음.
};

// ---------- (C) 소멸자 + 이동을 명시적으로 default ----------
struct ItemBagExplicit {
    std::string name;
    static inline int copyCount = 0;
    static inline int moveCount = 0;

    explicit ItemBagExplicit(std::string n) : name(std::move(n)) {}

    ~ItemBagExplicit() {}

    ItemBagExplicit(const ItemBagExplicit& other) : name(other.name) {
        ++copyCount;
    }
    ItemBagExplicit& operator=(const ItemBagExplicit& other) {
        name = other.name;
        ++copyCount;
        return *this;
    }

    ItemBagExplicit(ItemBagExplicit&& other) noexcept : name(std::move(other.name)) {
        ++moveCount;
    }
    ItemBagExplicit& operator=(ItemBagExplicit&& other) noexcept {
        name = std::move(other.name);
        ++moveCount;
        return *this;
    }
};

template <typename T>
void FillAndReport(const char* label, int n) {
    std::vector<T> bag;
    for (int i = 0; i < n; ++i) {
        bag.push_back(T(std::to_string(i)));
    }
    std::cout << label
              << " is_move_constructible=" << std::is_move_constructible<T>::value
              << " copyCount=" << T::copyCount
              << " moveCount=" << T::moveCount
              << "\n";
}

int main() {
    std::cout << "[static-check] ItemBagLoud: is_nothrow_move_constructible="
              << std::is_nothrow_move_constructible<ItemBagLoud>::value << "\n";
    std::cout << "[static-check] ItemBagCopyOnly: is_nothrow_move_constructible="
              << std::is_nothrow_move_constructible<ItemBagCopyOnly>::value << "\n";
    std::cout << "[static-check] ItemBagExplicit: is_nothrow_move_constructible="
              << std::is_nothrow_move_constructible<ItemBagExplicit>::value << "\n";

    FillAndReport<ItemBagLoud>("[ItemBagLoud]    ", 5);
    FillAndReport<ItemBagCopyOnly>("[ItemBagCopyOnly]", 5);
    FillAndReport<ItemBagExplicit>("[ItemBagExplicit]", 5);

    return 0;
}
내 리뷰 · C++
내 답안 · 자동 저장

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