C++ Is Movable Check static_assert

std::is_move_constructible과 그외 다수

Posted by Start Bootstrap on November 28, 2025

이동 생성자와 복사 생성자의 사용 의도에 관한 고민

class를 작성 하다 보면 가장 먼저 하는 고민 중 하나는 부가 적인 생성자의 지원 여부 입니다. Pool을 지원하는 class 혹은 많은 책임을 지고 단일을 유지 해야 하는 Manager class의 경우 데이터의 이동/복사를 허용하지 않는 경우가 많습니다. 때로는 성능이나 noexcept 문제로 이동만 지원하고 복사를 허용하지 않는 경우도 있습니다. 혹은 이동과 복사를 지원해서 std의 container 사용을 원활하게 할 수도 있습니다. 결론은 class의 의도에 따라 이동/복사 생성 대입 연산자가 달라진 다는 것입니다. class를 작성하는 저는 그 의도를 머릿속에 가지고 있습니다. 하지만 안타까운 점은 AI가 발전함에도 아직 제 머릿속을 볼 수 있는 방법은 쉽지 않다는 것일것 같습니다. 의도는 코드에 드러나야 합니다. 특히 그 의도가 런타임에 알아챌 수 있는 것보다 컴파일 타임에 드러나는 것이 더욱 바람직합니다.

코드의 주석은 힘이 없습니다. 보통 assert를 통해서 암시적 검증도 할 수 있지만 코드의 의도를 드러낼 수도 있습니다. 이 두가지 사안을 종합해 볼때 static_assert를 사용해서 의도를 드러내면 가장 좋아 보이네요. 예를 들면 나는 이 class의 이동/복사를 의도하지 않았다. 혹은 이 class의 이동/복사를 추가하려면 내 의도가 아니기 때문에 다시 생각해 봐야 한다를 static_assert로 표현할 수 있어야 합니다. 그러면은 옆 사람에게 일일이 제 코드를 설명하지 않아도 되서 좋을텐데요.

is_move_constructible

이동 생성 가능한 유형인지 판단하는 trait class가 있습니다. 대표적으로 std::is_move_constructible 입니다. type_trait 헤더에서 참조 할 수 있는데 간단하게 기능을 표로 정리하면 아래와 같습니다.

copy / move / construct / assign

의미 타입 트레이트
복사 생성자 가능 std::is_copy_constructible_v<T>
복사 대입 연산자 가능 std::is_copy_assignable_v<T>
이동 생성자 가능 std::is_move_constructible_v<T>
이동 대입 연산자 가능 std::is_move_assignable_v<T>

직접 default 연산자들을 구현 / delete 하는 코드를 만들어서 확인해보겠습니다. 언리얼 엔진의 내부 코드에 괜찮은 #define이 있어서 해당 코드를 참조해서 작성 해봤습니다.

        
            #include 

            #define HD_DISABLE_COPY(_name)					_name(const _name&) = delete
            #define HD_DISABLE_MOVE(_name)					_name(_name&&) = delete
            #define HD_DISABLE_ASSIGNMENT(_name)			_name& operator=(const _name&) = delete
            #define HD_DISABLE_MOVE_ASSIGNMENT(_name)		_name& operator=(_name&&) = delete

            #define HD_ENABLE_COPY(_name)					_name(const _name&) = default
            #define HD_ENABLE_MOVE(_name)					_name(_name&&) = default
            #define HD_ENABLE_ASSIGNMENT(_name)				_name& operator=(const _name&) = default
            #define HD_ENABLE_MOVE_ASSIGNMENT(_name)		_name& operator=(_name&&) = default


            class DisableClass
            {
            public:
                DisableClass( void ) noexcept = default;
                HD_DISABLE_COPY( DisableClass );
                HD_DISABLE_MOVE( DisableClass );
                HD_DISABLE_ASSIGNMENT( DisableClass );
                HD_DISABLE_MOVE_ASSIGNMENT( DisableClass );
            };

            class EnableClass
            {
            public:
                EnableClass(void) noexcept = default;
                HD_ENABLE_COPY(EnableClass);
                HD_ENABLE_MOVE(EnableClass);
                HD_ENABLE_ASSIGNMENT(EnableClass);
                HD_ENABLE_MOVE_ASSIGNMENT(EnableClass);
            };
        
    
static_assert static_assert result

해당 방식으로 코드에서 의도를 드러낼 수 있습니다. 물론 이 의도는 사소하고 특히 개인 작업에서는 큰 의미가 없을 수도 있습니다. 하지만 여러 사람과 코드를 작성하는 경우에는 어느 정도 의미가 있을 수도 있다고 생각합니다. 주석보다는 강력하고 무엇보다 컴파일 타임에 해당 의도를 알 수 있다는 것이 장점일 수 있어보이네요!

declval

해당 template의 구현 원리는 declval에 있습니다. declval 기능은 실제로 생성자를 호출해서 객체를 만들지 않고도 타입 의 rvalue 참조(T&&)를 확인할 수 있는 기능입니다. 특징은 선언은 있지만 정의가 없어서 호출하면 에러가 발생합니다. 사용은 decltype(std::declval() = std::declval()) 해당 방식으로 이용할 수 있으며 위의 소개한 Template 기능은 전부 declval의 기능으로 체크할 수 있습니다.