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
-
댓글 입력