C++ enum 사용중 문제

조회수 1934회
#include <iostream>

struct Tag
{

    enum class A {
        testA
    };

    enum class B {
        testB
    };
};

class Test {

    Tag m_Tag;

    Test(Tag tag) : m_Tag(tag) {}
};

int main()
{

        Test* test = new Test(Tag::A::testA);

}

Test 클래스에서 각각 A, B Tag 두 개를 가지는게 아닌 m_Tag 변수 하나를 가지고 두 가지 중 하나를 가지고 싶어서 만든 코드인데 위의 코드를 컴파일 성공시키려 타입 캐스팅 변환해도 잘안되는데 어떻게 방법이 없을까요?

아니면 다른 구조로 만드는 방법이 있을까요?

2 답변

  • 안녕하세요. 재미있는 아이디어로 보여서 한번 구현을 해보았습니다ㅎㅎ 원하시는 방향이 맞을진 모르겠지만... 템플릿을 이용하면 구현이 가능하긴 합니다.

    그런데 사실 이런 식의 구조는 그다지 유용하게 쓰일 순 없을 것으로 생각됩니다. enum class의 존재 목적 자체가 컴파일타임에 확실하게 타입을 알 수 있는 열거형을 위한 것이기 때문입니다.


    일단 예외처리 등을 전부 생략하고 간단하게 구현해보면 아래와 같이 구현할 수 있습니다.

    #include <iostream>
    
    struct Tag
    {
        enum class A {
            testA1,
            testA2
        };
        enum class B {
            testB
        };
    
        template<typename T> T Get() const {
            return static_cast<T>(m_Value);
        }
        template<typename T> static Tag Set(T value) {
            Tag ret;
            ret.m_Value = static_cast<int>(value);
            return ret;
        }
    private:
        int m_Value;
    };
    
    class Test
    {
    public:
        Test(Tag tag) : m_Tag(tag) {}
    
        template<typename T> T GetTag() {
            return m_Tag.Get<T>();
        }
    private:
        Tag m_Tag;
    };
    
    
    int main()
    {
        Test* test = new Test(Tag::Set(Tag::A::testA2));
    
        // 저장된 값을 제대로 불러와서 "tastA2"가 출력됩니다.
        Tag::A tagA = test->GetTag<Tag::A>();
        switch (tagA) {
        case Tag::A::testA1: std::cout << "tastA1" << std::endl; break;
        case Tag::A::testA2: std::cout << "tastA2" << std::endl; break;
        }
    
        delete test;
        return 0;
    }
    

    템플릿에 대한 이해가 전혀 없지 않다면 위의 코드는 따로 설명드리지 않아도 쉽게 이해하실 수 있을 것 같습니다.


    하지만, 위 코드는 사실 실제로 쓰기엔 너무 안전하지 않은 코드이기 때문에(잘못된 Get 호출이 가능해서) 안전성을 강화하여 추가적인 구현을 해보았습니다.

    • TMP(Template Meta Programming), RTTI(Run Time Type Information), Factory pattern, type_traits 등의 기법을 이용하였기 때문에 복잡해 보일 수 있습니다.
    • 여유가 되신다면 이 기법들에 대해서 구글링 등을 통해 공부해보시면 좋을 듯 합니다.

    아래 코드는 C++17문법이 들어가 있기 때문에 Visual Studio 2017에서만 컴파일이 가능합니다.

    • 만약 Visual Studio 2015라면, is_same_v<T, A> 등의 구문을 is_same<T, A>::value의 형식으로 변경해주면 컴파일 할 수 있습니다.
    • 만약 Visual Studio 2013 이하의 컴파일러라면 TMP와 RTTI 구문들을 전부 제거하면 컴파일 할 수 있습니다.
    #include <iostream>
    #include <typeinfo>
    #include <typeindex>
    #include <type_traits>
    
    struct Tag
    {
        enum class A {
            testA1,
            testA2
        };
        enum class B {
            testB
        };
    
        template<typename T, typename = std::enable_if_t<
            std::is_same_v<T, A> ||
            std::is_same_v<T, B>>>
        T Get() const
        {
            // 템플릿 메타프로그래밍과 RTTI를 이용하여
            // 타입에 대한 안전성을 보장할 수 있습니다.
            if (std::type_index(typeid(T)) != m_Type)
            {
                // throw를 통한 예외처리를 넣는게 더 안전하지만,
                // 예외핸들링까지 구현하면 설명할게 너무 많아지므로 생략합니다.
                std::cout
                    << "경고: 저장될 때와 다른 타입으로 가져옵니다." << std::endl
                    << " - 저장된 타입: " << m_Type.name() << std::endl
                    << " - 가져올 타입: " << typeid(T).name() << std::endl;
            }
            return static_cast<T>(m_Value);
        }
        template<typename T, typename = std::enable_if_t<
            std::is_same_v<T, A> ||
            std::is_same_v<T, B>>>
        static Tag Set(T value)
        {
            // 팩토리 패턴을 이용하여
            // Tag클래스 자체는 템플릿이 아니더라도 템플릿에 연관된
            // 인스턴스를 생성해낼 수 있습니다.
            Tag ret;
            ret.m_Type = typeid(T);
            ret.m_Value = static_cast<int>(value);
            return ret;
        }
    private:
        std::type_index m_Type  = typeid(m_Value);
        int             m_Value = 0;
    };
    
    
    class Test
    {
    public:
        Test(Tag tag) : m_Tag(tag) {}
    
        template<typename T>
        T GetTag()
        {
            return m_Tag.Get<T>();
        }
    private:
        Tag m_Tag;
    };
    
    
    int main()
    {
        Test* test1 = new Test(Tag::Set(Tag::A::testA2));
    
        // 저장된 값을 제대로 불러와서 "tastA2"가 출력됩니다.
        Tag::A tagA = test1->GetTag<Tag::A>();
        switch (tagA)
        {
        case Tag::A::testA1:
            std::cout << "tastA1" << std::endl;
            break;
        case Tag::A::testA2:
            std::cout << "tastA2" << std::endl;
            break;
        }
    
        // 저장된 타입이 아닌 타입으로 가져오려 하면
        // 경고 메시지 출력됩니다.
        Test* test2 = new Test(Tag::Set(Tag::B::testB));
        tagA = test2->GetTag<Tag::A>();
    
        delete test1;
        delete test2;
        return 0;
    }
    
  • 많은 정보 감사합니다. C++17에는 아직 익숙하지 않아 새로운 키워드가 많네요. 하지만 시간이 지나고 제가 생각해본 결과도 좋은 열거형 사용법이 아닌 것 같아 다른 방법으로 사용하게 되었습니다. 작성해주신 방법으로도 실행해보니 잘 실행되지만 저 방법은 쓰지 않는게 ㅜㅜ

    • (•́ ✖ •̀)
      알 수 없는 사용자
    • 넵 저도 재미삼아 만들어 봤지, 실제로 쓰기엔 별로 좋지 않은 코드입니다ㅎㅎ 다만, 여기에 쓰인 다양한 기법들이 언젠간 필요하게 되실 수 있으니, 나중에 여유가 되신다면 참고해서 공부하셔도 좋을 것 같습니다. Subin Park 2017.3.20 20:28
    • 네 공부하도록 하겠습니다. 감사합니다 ^^ 알 수 없는 사용자 2017.3.20 20:52

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)