CodeNote
Coding Note
CodeNote
전체 방문자
오늘
어제
  • 전체 보기 (35)
    • C++ (33)
      • Modern C++ (12)
      • Modern C++ STL (0)
      • Thread (16)
      • Thread (Async) (5)
    • 디자인패턴 (0)
    • Algorithm (2)
    • Electron (0)
    • Python (0)

블로그 메뉴

  • 홈
  • Github
  • 태그
  • 방명록

공지사항

인기 글

태그

  • mutex
  • C++ #Memory
  • C++
  • LOCK
  • 자료구조
  • Free

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
CodeNote
C++/Modern C++

[C++17] std::variant

[C++17] std::variant
C++/Modern C++

[C++17] std::variant

2022. 11. 7. 17:58

개요

이전에 Union은 몇 가지 문제를 가지고 있었다. 

  • int를 써놓고 double을 읽는 행위 (undefined behavior)
  • Object가 들어갔을 때 타입을 바꿀때마다 직접적으로 생성자와 소멸자를 호출해줘야한다.

이러한 문제들을 해결해 줄 수 있는 것이 C++ 17 부터 추가된 std::variant인데 훨씬 더 안전한 union을 사용할 수 있도록 만들어준다.

 

std::variant 예제

struct S // 4 +(4) + 8 + 4 + (4) =  24
{
    int i; // 4
    double d; // 8
    float f; // 4
};

union U // 8
{
    int i; // 4
    double d; // 8
    float f; // 4
};

std::variant<int, double, float> v;
std::cout << "S" << sizeof(S) << std::endl; // 24 byte
std::cout << "U" << sizeof(U) << std::endl; // 8 byte
std::cout << "V" << sizeof(v) << std::endl; // 16 byte

std::variant<int, double, float> v

결과를 보면 v의 크기는 16 byte다. 이유는 std::variant 자체도 변수가 어떤 타입인지 추적을 해야하기 때문에 union처럼 하나의 메모리 공간을 같이 사용하고 있음에도 뒷부분에 데이터 타입이 따로 들어있다. 데이터 타입으로 8 byte가 추가되므로 총 16 byte가 되는 것이다.

 

Union vs std::variant

  • 이전에 union에서는
U u;
u.i = 10;
std::cout << u.d << std::endl; // undefined behavior

int를 쓰고 double로 읽는 행위를 하게되면 undefined behavior로 예상치 못한 결과가 나온다.

 

  • 똑같은 코드를 std::variant를 사용해보면
std::variant<int, double, float> v;
v = 10;
try { 
    std::cout << std::get<double>(v) << std::endl; 
} catch (...) { }

Exception이 던져진다. try / catch 구문을 통해 얼마든지 핸들링이 가능하다는 의미이다.

 

try / catch가 싫다면 if문을 사용할 수 있다.

if (auto pVal = std::get_if<double>(&v))
{
    std::cout << *pVal << std::endl;
}
else
{
    std::cout << "v is not type double" << std::endl;
}

현재 v 가 double 타입인지 체크하고 value를 출력할 수 있다. 즉, std::variant를 사용하게 되면 현재 어떤 타입이 들어있는지 추적이 가능하기 때문에 안전한 코드를 만들 수 있게 도와준다.

 

또한, union에 오브젝트 타입이 들어가게되면 직접 생성자와 소멸자를 호출해야했다.

하지만 std::variant를 사용하게 되면,

std::variant<std::string, std::vector<int>> sv;
sv = std::string(L"abcd");
std::cout << std::get<std::string>(v) << std::endl;

sv = std::vector{ 1,2,3 };

직접적으로 생성자 또는 소멸자를 호출할 필요없이 vector를 넣어주면 자동적으로 생성자와 소멸자를 관리해주면서 문제없이 vector를 사용할 수 있게된다.

 

Error Code Return 사용

std::variant는 union을 대체하는 것뿐만 아니라 ErrorCode를 리턴해주는데 사용할 수도 있다.

// enum class ErrorCode
std::variant<int ,ErrorCode> divide(int a, int b)
{
    if (b == 0)
    {
        return ErrorCode::divide0;
    }
    return a / b;
}

std::optional을 사용하는 방법도 있으나 std::optional은 타입 자체가 유효한지 아닌지만 리턴해줄 수 있기 때문에 더 많은 Error 정보를 리턴해줄 때는 std::tuple이나 std::pair를 사용하는 방법이 있었다. 이를 std::variant를 사용하게 되면 똑같은 메모리 공간을 int와 ErrorCode가 공유하면서 리턴해줄 수 있다.

 

int와 ErrorCode는 같은 공간을 공유해도 문제가 없다. divide함수가 유효하다면 a / b 결과인 int 타입을 넣어주면 되고 그렇지 않다면 에러코드를 넣어서 리턴해주면 되기 때문에 메모리는 공유하지만 std::tuple 처럼 사용할 수 있다.

 

std::variant가 메모리를 공유함에도 불구하고 std::pair나 std::tuple과 아무런 차이없이 사용할 수 있는 것이다.

 

결론

std::variant는 union이 훨씬 더 안전해졌지만 내부적으로 타입 추적이 들어가고 호출때마다  타입을 체크해주어야하기 때문에 어느정도 오버헤드가 있는 것은 사실이다. 개발환경에 따라 이 정도는 무시할만하다고 생각된다면 어찌되었건 std::variant을 사용하는 것이 좋다.

'C++ > Modern C++' 카테고리의 다른 글

[C++] Type Punning (타입 장난)  (0) 2022.11.10
[C++] Union  (0) 2022.11.04
[C++17] std::optional  (0) 2022.10.31
[C++] 가상함수 원리  (0) 2022.10.24
[C++] attributes(속성)  (0) 2022.10.13
  • 개요
  • std::variant 예제
  • Union vs std::variant
  • Error Code Return 사용
  • 결론
'C++/Modern C++' 카테고리의 다른 글
  • [C++] Type Punning (타입 장난)
  • [C++] Union
  • [C++17] std::optional
  • [C++] 가상함수 원리
CodeNote
CodeNote
기록 블로그

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.