C++ Structured Binding | "구조적 바인딩" C++17 가이드
이 글의 핵심
C++ Structured Binding에 대한 실전 가이드입니다.
들어가며
Structured Binding (구조적 바인딩)은 C++17에서 도입된 기능으로, 튜플, 배열, 구조체 등을 여러 변수로 한 번에 분해할 수 있습니다.
#include <tuple>
#include <iostream>
#include <string>
int main() {
// tuple 생성: 여러 타입의 값을 하나로 묶음
std::tuple<int, double, std::string> t = {42, 3.14, "Hello"};
// 구조적 바인딩 (Structured Binding, C++17)
// auto [i, d, s]: tuple의 각 요소를 개별 변수로 분해
// i, d, s는 각각 tuple의 첫 번째, 두 번째, 세 번째 요소
// 타입은 자동 추론: i는 int, d는 double, s는 string
auto [i, d, s] = t;
std::cout << i << std::endl; // 42
std::cout << d << std::endl; // 3.14
std::cout << s << std::endl; // Hello
}
왜 필요한가?:
- 간결성: 여러 변수를 한 줄로 선언
- 가독성: 의미 있는 이름 부여
- 안전성: 타입 추론으로 실수 방지
- 편의성: 맵 순회, 함수 반환값 처리
// ❌ 기존 방식: 복잡
std::map<std::string, int> m;
for (const auto& pair : m) {
std::cout << pair.first << ": " << pair.second << '\n';
}
// ✅ 구조적 바인딩: 간결
for (const auto& [key, value] : m) {
std::cout << key << ": " << value << '\n';
}
1. 기본 사용
배열
#include <iostream>
int main() {
int arr[] = {1, 2, 3};
auto [a, b, c] = arr;
std::cout << a << std::endl; // 1
std::cout << b << std::endl; // 2
std::cout << c << std::endl; // 3
return 0;
}
구조체
#include <iostream>
struct Point {
int x;
int y;
};
int main() {
Point p = {10, 20};
auto [x, y] = p;
std::cout << "x: " << x << std::endl; // 10
std::cout << "y: " << y << std::endl; // 20
return 0;
}
튜플
#include <tuple>
#include <iostream>
#include <string>
std::tuple<int, double, std::string> getData() {
return {42, 3.14, "Hello"};
}
int main() {
auto [i, d, s] = getData();
std::cout << "int: " << i << std::endl;
std::cout << "double: " << d << std::endl;
std::cout << "string: " << s << std::endl;
return 0;
}
2. 참조와 const
복사 vs 참조
#include <iostream>
struct Point {
int x, y;
};
int main() {
Point p = {10, 20};
// 방법 1: 복사 (auto)
// x1, y1은 p.x, p.y의 복사본
// x1, y1 변경해도 p는 영향 없음
auto [x1, y1] = p;
x1 = 100; // x1만 변경 (p.x는 여전히 10)
std::cout << "p.x: " << p.x << std::endl; // 10
// 방법 2: 참조 (auto&)
// x2, y2는 p.x, p.y의 참조 (별칭)
// x2, y2 변경하면 p도 변경됨
auto& [x2, y2] = p;
x2 = 100; // p.x가 100으로 변경됨
std::cout << "p.x: " << p.x << std::endl; // 100
// 방법 3: const 참조 (const auto&)
// x3, y3는 p.x, p.y의 const 참조
// 읽기만 가능, 수정 불가 (불필요한 복사 방지)
const auto& [x3, y3] = p;
// x3 = 200; // ❌ 컴파일 에러: const 참조는 수정 불가
std::cout << "x3: " << x3 << std::endl; // 100
return 0;
}
출력:
p.x: 10
p.x: 100
x3: 100
3. 실전 예제
예제 1: 맵 순회
#include <map>
#include <iostream>
#include <string>
int main() {
std::map<std::string, int> scores = {
{"Alice", 90},
{"Bob", 85},
{"Charlie", 95}
};
std::cout << "=== 구조적 바인딩 ===" << std::endl;
// const auto& [name, score]: map의 각 요소(pair)를 분해
// name: pair.first (키)
// score: pair.second (값)
// const auto&: 복사 없이 const 참조로 접근 (효율적)
for (const auto& [name, score] : scores) {
// pair.first, pair.second 대신 의미 있는 이름 사용
std::cout << name << ": " << score << std::endl;
}
std::cout << "\n=== 기존 방식 ===" << std::endl;
// 기존 방식: pair 객체로 접근
// pair.first, pair.second는 의미가 불명확
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
출력:
=== 구조적 바인딩 ===
Alice: 90
Bob: 85
Charlie: 95
=== 기존 방식 ===
Alice: 90
Bob: 85
Charlie: 95
예제 2: 함수 반환값
#include <tuple>
#include <iostream>
std::tuple<int, int, int> getRGB() {
return {255, 128, 64};
}
std::pair<int, int> divmod(int dividend, int divisor) {
return {dividend / divisor, dividend % divisor};
}
int main() {
auto [r, g, b] = getRGB();
std::cout << "R: " << r << std::endl;
std::cout << "G: " << g << std::endl;
std::cout << "B: " << b << std::endl;
auto [quotient, remainder] = divmod(17, 5);
std::cout << "17 / 5 = " << quotient << " ... " << remainder << std::endl;
return 0;
}
출력:
R: 255
G: 128
B: 64
17 / 5 = 3 ... 2
예제 3: pair 언팩
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
auto [minIt, maxIt] = std::minmax_element(v.begin(), v.end());
std::cout << "최소: " << *minIt << std::endl; // 1
std::cout << "최대: " << *maxIt << std::endl; // 9
// 삽입 결과
std::map<std::string, int> m;
auto [it, inserted] = m.insert({"key", 10});
if (inserted) {
std::cout << "삽입 성공: " << it->first << " = " << it->second << std::endl;
}
return 0;
}
출력:
최소: 1
최대: 9
삽입 성공: key = 10
예제 4: 구조체 분해
#include <vector>
#include <iostream>
#include <string>
struct Person {
std::string name;
int age;
double height;
};
int main() {
std::vector<Person> people = {
{"Alice", 25, 165.5},
{"Bob", 30, 175.0},
{"Charlie", 28, 180.5}
};
for (const auto& [name, age, height] : people) {
std::cout << name << " (" << age << "세, " << height << "cm)" << std::endl;
}
return 0;
}
출력:
Alice (25세, 165.5cm)
Bob (30세, 175cm)
Charlie (28세, 180.5cm)
4. 자주 발생하는 문제
문제 1: 요소 개수 불일치
#include <tuple>
int main() {
std::tuple<int, int, int> t = {1, 2, 3};
// ❌ 에러: 3개인데 2개만
// auto [a, b] = t;
// ✅ 개수 일치
auto [a, b, c] = t;
return 0;
}
문제 2: 참조 수명
#include <iostream>
struct Point {
int x, y;
};
int main() {
// ❌ 댕글링 참조
// auto& [x, y] = Point{10, 20}; // 임시 객체
// x, y는 댕글링 참조!
// ✅ 복사
auto [x1, y1] = Point{10, 20};
std::cout << "x1: " << x1 << ", y1: " << y1 << std::endl;
// ✅ 변수 저장
Point p = {10, 20};
auto& [x2, y2] = p;
x2 = 100;
std::cout << "p.x: " << p.x << std::endl; // 100
return 0;
}
출력:
x1: 10, y1: 20
p.x: 100
문제 3: 타입 추론
#include <tuple>
int main() {
// ❌ 타입 명시 불가
// auto [int x, double y] = std::tuple{1, 2.0}; // 에러
// ✅ auto만 가능
auto [x, y] = std::tuple{1, 2.0};
return 0;
}
5. 실무 패턴
패턴 1: 에러 처리
#include <iostream>
#include <string>
#include <fstream>
std::pair<bool, std::string> parseConfig(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
return {false, "파일 없음"};
}
// 파싱 로직
std::string content;
if (!std::getline(file, content)) {
return {false, "파싱 실패"};
}
return {true, "성공"};
}
int main() {
auto [success, message] = parseConfig("config.json");
if (!success) {
std::cerr << "에러: " << message << std::endl;
return 1;
}
std::cout << "결과: " << message << std::endl;
return 0;
}
패턴 2: 다중 반환값
#include <tuple>
#include <iostream>
std::tuple<int, int, int> divmod(int dividend, int divisor) {
int quotient = dividend / divisor;
int remainder = dividend % divisor;
int sign = (dividend < 0) ^ (divisor < 0) ? -1 : 1;
return {quotient, remainder, sign};
}
int main() {
auto [q, r, s] = divmod(17, 5);
std::cout << "몫: " << q << ", 나머지: " << r << ", 부호: " << s << std::endl;
// 몫: 3, 나머지: 2, 부호: 1
auto [q2, r2, s2] = divmod(-17, 5);
std::cout << "몫: " << q2 << ", 나머지: " << r2 << ", 부호: " << s2 << std::endl;
// 몫: -3, 나머지: -2, 부호: -1
return 0;
}
출력:
몫: 3, 나머지: 2, 부호: 1
몫: -3, 나머지: -2, 부호: -1
패턴 3: 범위 기반 for
#include <map>
#include <vector>
#include <iostream>
#include <string>
int main() {
std::map<std::string, std::vector<int>> data = {
{"A", {1, 2, 3}},
{"B", {4, 5, 6}},
{"C", {7, 8, 9}}
};
// 맵 순회
std::cout << "=== 읽기 ===" << std::endl;
for (const auto& [key, values] : data) {
std::cout << key << ": ";
for (int v : values) {
std::cout << v << ' ';
}
std::cout << std::endl;
}
// 수정
std::cout << "\n=== 수정 ===" << std::endl;
for (auto& [key, values] : data) {
values.push_back(0); // 각 벡터에 0 추가
}
for (const auto& [key, values] : data) {
std::cout << key << ": ";
for (int v : values) {
std::cout << v << ' ';
}
std::cout << std::endl;
}
return 0;
}
출력:
=== 읽기 ===
A: 1 2 3
B: 4 5 6
C: 7 8 9
=== 수정 ===
A: 1 2 3 0
B: 4 5 6 0
C: 7 8 9 0
6. 커스텀 타입 지원
#include <iostream>
class MyClass {
public:
int x, y;
MyClass(int x, int y) : x(x), y(y) {}
// 튜플 프로토콜 구현
template<size_t I>
auto& get() {
if constexpr (I == 0) return x;
else if constexpr (I == 1) return y;
}
template<size_t I>
const auto& get() const {
if constexpr (I == 0) return x;
else if constexpr (I == 1) return y;
}
};
// 특수화
namespace std {
template<>
struct tuple_size<MyClass> : integral_constant<size_t, 2> {};
template<size_t I>
struct tuple_element<I, MyClass> {
using type = int;
};
}
int main() {
MyClass obj{10, 20};
auto [a, b] = obj;
std::cout << a << ", " << b << std::endl; // 10, 20
return 0;
}
7. 실전 예제: 데이터 처리
#include <vector>
#include <map>
#include <iostream>
#include <string>
#include <algorithm>
struct Student {
std::string name;
int score;
std::string grade;
};
std::map<std::string, std::vector<Student>> groupByGrade(const std::vector<Student>& students) {
std::map<std::string, std::vector<Student>> groups;
for (const auto& [name, score, grade] : students) {
groups[grade].push_back({name, score, grade});
}
return groups;
}
void printStatistics(const std::map<std::string, std::vector<Student>>& groups) {
for (const auto& [grade, students] : groups) {
int total = 0;
for (const auto& [name, score, g] : students) {
total += score;
}
double average = static_cast<double>(total) / students.size();
std::cout << "등급 " << grade << ": "
<< students.size() << "명, 평균 "
<< average << "점" << std::endl;
}
}
int main() {
std::vector<Student> students = {
{"Alice", 95, "A"},
{"Bob", 85, "B"},
{"Charlie", 92, "A"},
{"David", 78, "C"},
{"Eve", 88, "B"}
};
auto groups = groupByGrade(students);
std::cout << "=== 학생 목록 ===" << std::endl;
for (const auto& [grade, studentList] : groups) {
std::cout << "등급 " << grade << ":" << std::endl;
for (const auto& [name, score, g] : studentList) {
std::cout << " " << name << ": " << score << "점" << std::endl;
}
}
std::cout << "\n=== 통계 ===" << std::endl;
printStatistics(groups);
return 0;
}
출력:
=== 학생 목록 ===
등급 A:
Alice: 95점
Charlie: 92점
등급 B:
Bob: 85점
Eve: 88점
등급 C:
David: 78점
=== 통계 ===
등급 A: 2명, 평균 93.5점
등급 B: 2명, 평균 86.5점
등급 C: 1명, 평균 78점
정리
핵심 요약
- 구조적 바인딩: 튜플/배열/구조체 분해
- auto: 복사,
auto&: 참조,const auto&: const 참조 - 맵 순회:
for (const auto& [key, value] : map) - 함수 반환:
auto [a, b] = func() - 성능: 컴파일 타임, 오버헤드 없음
지원 타입
| 타입 | 예시 | 설명 |
|---|---|---|
| 배열 | int arr[3] | 고정 크기 배열 |
| 튜플 | std::tuple<int, double> | std::tuple, std::pair |
| 구조체 | struct Point { int x, y; } | 집합체 (aggregate) |
| 커스텀 | get<I>() 구현 | 튜플 프로토콜 |
실전 팁
사용 원칙:
- 맵 순회:
const auto& [key, value] - 함수 반환:
auto [a, b] = func() - 읽기만:
const auto& - 수정:
auto&
성능:
- 컴파일 타임 처리
- 런타임 오버헤드 없음
- 복사 방지 (참조 사용)
- 가독성 향상
주의사항:
- 요소 개수 일치
- 참조 수명 관리
- 타입 명시 불가
- 중첩 분해 불가 (C++17)
다음 단계
- C++ Tuple
- C++ Range-based For
- C++ Auto Keyword
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Structured Binding 고급 | “구조화 바인딩” 가이드
- C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문
- C++ tuple apply | “튜플 적용” 가이드
관련 글
- C++ 범위 기반 for문과 구조화된 바인딩 | 모던 C++ 반복문
- C++ Structured Binding 고급 |
- C++ any |
- C++ auto 키워드 |
- C++ auto 타입 추론 | 복잡한 타입을 컴파일러에 맡기기