개발 관련

[C++] 이동 전용 클래스(Move-only Class)

silencecolor 2026. 3. 29. 03:29
반응형

복사는 불가능하고 오직 이동(move)만 가능한 클래스.

대표적인 예시로 std::unique_ptr이나 std::thread 가 그것이다.

 

이동전용 클래스를 만드는 법은, 복사 생성자와 복사 할당 연산자를 delete 키워드로 명시적 삭제하고, 이동 생성자와 이동 할당 연산자만 정의하면 된다.

코드 예시는 아래와 같다.

class MoveOnly {
public:
    MoveOnly() = default;

    // 1. 복사 생성 및 복사 할당 금지
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly& operator=(const MoveOnly&) = delete;

    // 2. 이동 생성 및 이동 할당 허용
    MoveOnly(MoveOnly&& other) noexcept {
        // 자원 이전 로직
    }
    MoveOnly& operator=(MoveOnly&& other) noexcept {
        if (this != &other) {
            // 기존 자원 해제 후 새 자원 이전
        }
        return *this;
    }
};

 


주요 특징

 

1. 소유권 독점

- 특정 시점에 자원의 소유자가 오직 하나임을 보장한다.

 

2. 성능 최적화

- 대용량 데이터를 복사하는 대신 포인터 주소만 옮기므로 비용이 매우 저렴하다.

 

3. 안전성

- 실수로 객체가 복사되어 발생하는 의도치 않은 부작용(예 : 이중해제)을 컴파일러 단계에서 막아준다.

 


 

주의 사항

 

1. std::move 사용

- 이동 전용 객체를 전달할 때는 반드시 std::move()를 사용하여 rvalue로 캐스팅 해야 한다.

 

2. STL 컨테이너

- 이동 전용 클래스는 복사가 불가능하므로, 이 클래스를 std::vector 같은 컨테이너가 재할당을 할 때 오직 이동에만 의존하게 된다. 이 때 사용자가 명시적으로 만든 이동 생성자에 noexcept를 붙여야 재 할당시 성능 이점을 얻을 수 있다. (noexcept가 없으면 컨테이너가 이동을 거부하여 에러가 발생할 수 있음)


클래스 안에 atomic이나 mutex가 쓰이는 경우?

 

기본적으로 std::mutex와 std::atomic은 복사(copy)와 이동(move)이 모두 금지된 타입이다.

클래스 멤버 중 하나라도 이동/복사가 불가능하면, 컴파일러는 자동으로 생성해주는(Default) 복사/이동 생성자 역시 삭제(delete) 처리된다.

 

1. std::mutex의 경우

- 뮤텍스는 특정 메모리 주소에 고정되어 운영체제의 리소스를 관리한다. 만약 복사나 이동을 허용해서 주소가 바뀌면, 이미 해당 뮤텍스를 기다리던 스레드들이 엉뚱한 곳을 참조하게 되어 데드락(Deadlock)이나 데이터 오염이 발생한다.

 

2. std::atomic의 경우

- 원자적 연산은 특정 메모리 위치에 대한 하드웨어적인 보장을 제공한다. 객체를 옮기거나 복사하는 과정에서 '원자성'을 유지하며 값을 복사하는 표준적인 방법은 없다. 그래서 설계상 금지가 되어 있다.

 


클래스의 영향

 

이러한 atomic이나 mutex 같은 멤버들을 클래스 안에 넣으면 컴파일러는 다음과 같이 판단한다.

- 복사할 수 없는 멤버가 있다? -> 복사 생성자/할당 연산자 삭제

- 이동 할 수 없는 멤버가 있다? -> 이동 생성자/할당 연산자 삭제

 

즉, atomic이나 mutex 같은 멤버가 포함된 클래스는 이동도 안되고 복사도 안되는 상태가 기본값이 되어버린다.

 


만약 이동전용으로라도 만들려면?

 

컴파일러가 포기한 이동 생성자와 이동 할당 연산자를 직접 구현해줘야 한다.

하지만 이때도 내부의 mutex나 atomic은 이동시키는 것이 아니라, 새 객체에서 새로 초기화 하거나 값을 꺼내서 옮기는 방식을 취해야 한다.

 


atomic이나 mutex 외에 더 있는가?

 

1. 동기화 및 스레드 관련 객체

- std::condition_variable : 특정 조건이 만족될 때까지 스레드를 대기시키는 객체. 주소가 바뀌면 대기중인 스레드들이 길을 잃는다.

- std::lock_guard (이동/복사 모두 안됨. 부동.)

- std::unique_lock : std::unique_ptr 처럼 이동 전용(move-only)

 

2. 입출력 스트림 (I/O Stream)

- std::ostream, std::istream : std::cout 이나 파일 스트림(std::fstream)등이 포함된다.

- 이유 : 한 파일을 두 객체가 동시에 제어하면 파일 포인터(위치)가 꼬여서 데이터가 오염될 수 있기 때문.

 

3. 하드웨어 리소스 추상화

- std::stop_source, std::stop_token : C++20에서 추가된 스레드 중단 메커니즘 관련 객체들.

 

4. 상수 멤버나 참조자 멤버(복사/이동 제약)

객체에 const 멤버나 &(참조자) 멤버가 있으면, 대입(Assignment) 연산자가 생성되지 않아, 결과적으로 객체의 값을 바꿀 수 없는 상태가 된다.

- const int x; 또는 int& ref; 가 멤버로 있다면, 한 번 초기화된 후에는 값을 덮어쓸(할당할) 수 없으므로 컴파일러가 기본 복사/이동 할당 연산자를 생성하지 않는다.

 

 

이러한 멤버들을 포함하면 해당 클래스는 "부동 객체(Immovable Object)"가 된다.

만약 이 클래스를 이동이라도 가능하게 만들고 싶다면, 직접 이동 생성자를 작성해 내부 리소스를 어떻게 옮길지 명시해야 한다.

반응형

'개발 관련' 카테고리의 다른 글

mutable  (0) 2026.03.21
const  (0) 2026.03.18
Stack, Heap  (0) 2026.03.14
[C++] decltype, declval  (0) 2026.03.12
C++20 Big 4  (0) 2026.03.02