C++ Union과 Variant | "타입 안전 공용체" 가이드
이 글의 핵심
C++ Union과 Variant에 대해 정리한 개발 블로그 글입니다. union Data { int i; float f; char c; };
union 기본
union Data {
int i;
float f;
char c;
};
int main() {
Data d;
d.i = 10;
cout << d.i << endl; // 10
d.f = 3.14f;
cout << d.f << endl; // 3.14
// d.i는 이제 유효하지 않음!
cout << sizeof(Data) << endl; // 4 (가장 큰 멤버)
}
union 문제점
// ❌ 타입 안전하지 않음
union Data {
int i;
float f;
};
Data d;
d.i = 10;
cout << d.f << endl; // 쓰레기 값 (UB)
// ❌ 생성자/소멸자 있는 타입 불가
union Bad {
string s; // 에러! (C++11 이전)
};
std::variant (C++17)
#include <variant>
int main() {
variant<int, double, string> v;
v = 10;
cout << get<int>(v) << endl; // 10
v = 3.14;
cout << get<double>(v) << endl; // 3.14
v = string("Hello");
cout << get<string>(v) << endl; // Hello
// 잘못된 타입 접근
try {
cout << get<int>(v) << endl; // 예외
} catch (const bad_variant_access& e) {
cout << "타입 불일치" << endl;
}
}
variant 방문자
#include <variant>
struct Visitor {
void operator()(int i) {
cout << "정수: " << i << endl;
}
void operator()(double d) {
cout << "실수: " << d << endl;
}
void operator()(const string& s) {
cout << "문자열: " << s << endl;
}
};
int main() {
variant<int, double, string> v;
v = 10;
visit(Visitor{}, v); // 정수: 10
v = 3.14;
visit(Visitor{}, v); // 실수: 3.14
v = string("Hello");
visit(Visitor{}, v); // 문자열: Hello
}
실전 예시
예시 1: JSON 값
#include <variant>
#include <map>
class JsonValue {
public:
using Value = variant<nullptr_t, bool, int, double, string,
vector<JsonValue>, map<string, JsonValue>>;
Value value;
JsonValue() : value(nullptr) {}
JsonValue(int i) : value(i) {}
JsonValue(double d) : value(d) {}
JsonValue(const string& s) : value(s) {}
bool isNull() const { return holds_alternative<nullptr_t>(value); }
bool isBool() const { return holds_alternative<bool>(value); }
bool isInt() const { return holds_alternative<int>(value); }
bool isDouble() const { return holds_alternative<double>(value); }
bool isString() const { return holds_alternative<string>(value); }
int asInt() const { return get<int>(value); }
double asDouble() const { return get<double>(value); }
string asString() const { return get<string>(value); }
};
int main() {
JsonValue v1 = 42;
JsonValue v2 = 3.14;
JsonValue v3 = string("Hello");
if (v1.isInt()) {
cout << v1.asInt() << endl;
}
}
예시 2: 상태 머신
struct Idle {};
struct Running { int progress; };
struct Paused { int savedProgress; };
struct Completed { string result; };
using State = variant<Idle, Running, Paused, Completed>;
class Task {
private:
State state;
public:
Task() : state(Idle{}) {}
void start() {
state = Running{0};
}
void pause() {
if (auto* r = get_if<Running>(&state)) {
state = Paused{r->progress};
}
}
void resume() {
if (auto* p = get_if<Paused>(&state)) {
state = Running{p->savedProgress};
}
}
void complete(const string& result) {
state = Completed{result};
}
void printState() {
visit( {
using T = decay_t<decltype(s)>;
if constexpr (is_same_v<T, Idle>) {
cout << "대기 중" << endl;
} else if constexpr (is_same_v<T, Running>) {
cout << "실행 중: " << s.progress << "%" << endl;
} else if constexpr (is_same_v<T, Paused>) {
cout << "일시정지: " << s.savedProgress << "%" << endl;
} else if constexpr (is_same_v<T, Completed>) {
cout << "완료: " << s.result << endl;
}
}, state);
}
};
int main() {
Task task;
task.printState(); // 대기 중
task.start();
task.printState(); // 실행 중: 0%
task.pause();
task.printState(); // 일시정지: 0%
}
예시 3: 에러 처리
template<typename T, typename E>
class Result {
private:
variant<T, E> data;
public:
Result(T value) : data(value) {}
Result(E error) : data(error) {}
bool isOk() const {
return holds_alternative<T>(data);
}
bool isErr() const {
return holds_alternative<E>(data);
}
T& value() {
return get<T>(data);
}
E& error() {
return get<E>(data);
}
};
Result<int, string> divide(int a, int b) {
if (b == 0) {
return string("0으로 나눌 수 없음");
}
return a / b;
}
int main() {
auto result = divide(10, 2);
if (result.isOk()) {
cout << "결과: " << result.value() << endl;
} else {
cout << "에러: " << result.error() << endl;
}
}
예시 4: 플래그 집합
#include <bitset>
enum class Feature {
FEATURE_A,
FEATURE_B,
FEATURE_C,
FEATURE_D,
FEATURE_COUNT
};
class FeatureFlags {
private:
bitset<static_cast<size_t>(Feature::FEATURE_COUNT)> flags;
public:
void enable(Feature f) {
flags.set(static_cast<size_t>(f));
}
void disable(Feature f) {
flags.reset(static_cast<size_t>(f));
}
bool isEnabled(Feature f) const {
return flags.test(static_cast<size_t>(f));
}
void print() const {
cout << "플래그: " << flags << endl;
}
};
int main() {
FeatureFlags features;
features.enable(Feature::FEATURE_A);
features.enable(Feature::FEATURE_C);
features.print(); // 0101
if (features.isEnabled(Feature::FEATURE_A)) {
cout << "기능 A 활성화" << endl;
}
}
variant vs union
| 특성 | union | variant |
|---|---|---|
| 타입 안전 | ❌ | ✅ |
| 복잡한 타입 | 제한적 | ✅ |
| 현재 타입 추적 | 수동 | 자동 |
| 성능 | 빠름 | 약간 느림 |
자주 발생하는 문제
문제 1: union 타입 추적
// ❌ 타입 추적 안함
union Data {
int i;
float f;
};
Data d;
d.i = 10;
// 나중에 i인지 f인지 모름!
// ✅ 태그 추가
struct TaggedData {
enum Type { INT, FLOAT } type;
union {
int i;
float f;
};
};
// ✅ variant 사용 (더 좋음)
variant<int, float> data;
문제 2: variant 예외
// ❌ 예외 발생
variant<int, double> v = 10;
double d = get<double>(v); // bad_variant_access
// ✅ get_if 사용
if (double* p = get_if<double>(&v)) {
cout << *p << endl;
} else {
cout << "타입 불일치" << endl;
}
// ✅ holds_alternative
if (holds_alternative<int>(v)) {
cout << get<int>(v) << endl;
}
문제 3: 비트 필드 패딩
struct Flags {
unsigned int a : 1;
unsigned int b : 1;
// 컴파일러가 패딩 추가 가능
};
cout << sizeof(Flags) << endl; // 4 (예상: 1)
FAQ
Q1: union은 언제 사용하나요?
A:
- 메모리 제약
- C 호환성
- 저수준 프로그래밍
Q2: variant는 언제 사용하나요?
A:
- 타입 안전 필요
- 여러 타입 중 하나
- 에러 처리 (Result 타입)
Q3: union vs variant 성능?
A: union이 약간 빠르지만, 대부분 무시할 수 있습니다.
Q4: 비트 필드는 언제 사용하나요?
A:
- 하드웨어 레지스터
- 네트워크 프로토콜
- 메모리 최적화
Q5: variant 오버헤드는?
A: 타입 인덱스 저장 (보통 1바이트)과 정렬 패딩입니다.
Q6: Union/Variant 학습 리소스는?
A:
- cppreference.com
- “Effective Modern C++”
- “C++17: The Complete Guide”
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ variant | “타입 안전 union” 가이드
- C++ PMR | “다형 메모리 리소스” 가이드
- C++ Visitor Pattern | “방문자 패턴” 가이드
관련 글
- C++ std::variant vs union |
- C++ variant |
- C++ Algorithm Set |
- C++ any |
- 모던 C++ (C++11~C++20) 핵심 문법 치트시트 | 현업에서 자주 쓰는 한눈에 보기