
std::initializer_list
개요
std::initializer_list는 C++11에서 도입된 템플릿 클래스로, 중괄호 초기화 리스트(braced-init-list)를 표현하는 데 사용됩니다. 이를 통해 함수에서 배열과 유사한 구문으로 여러 값을 전달할 수 있어 코드를 더 직관적이고 간결하게 만들 수 있습니다.
C++ 버전별 도입 시기
- C++11:
std::initializer_list도입 - C++14: 개선된 기능들
- C++17: 구조적 바인딩과의 호환성 개선
- C++20: 더 나은 추론 지원
std::initializer_list의 특징
기본 구조
template<class T>
class initializer_list {
public:
using value_type = T;
using reference = const T&;
using const_reference = const T&;
using size_type = size_t;
size_type size() const noexcept;
const T* begin() const noexcept;
const T* end() const noexcept;
};
주요 특징
- 읽기 전용: 요소를 수정할 수 없습니다
- 가벼운 래퍼: 실제 데이터는 스택에 저장됩니다
- 범위 기반 for 루프 지원: 쉽게 순회할 수 있습니다
사용 방법
1. 함수 파라미터로 사용
#include <initializer_list>
#include <vector>
#include <iostream>
void printNumbers(std::initializer_list<int> numbers) {
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
}
int main() {
printNumbers({1, 2, 3, 4, 5});
printNumbers({10, 20, 30});
return 0;
}
2. 생성자에서 사용
class MyContainer {
private:
std::vector<int> data;
public:
MyContainer(std::initializer_list<int> init)
: data(init.begin(), init.end()) {}
void print() const {
for (int val : data) {
std::cout << val << " ";
}
std::cout << std::endl;
}
};
int main() {
MyContainer container{1, 2, 3, 4, 5};
container.print();
return 0;
}
3. STL 컨테이너 초기화
#include <vector>
#include <map>
#include <string>
int main() {
// 벡터 초기화
std::vector<int> vec{1, 2, 3, 4, 5};
// 맵 초기화
std::map<std::string, int> scores{
{"Alice", 95},
{"Bob", 87},
{"Charlie", 92}
};
return 0;
}
std::initializer_list vs std::vector
std::initializer_list의 장점
- 간결한 문법:
{}를 사용한 직관적인 초기화 - 컴파일 타임 최적화: 불필요한 복사가 없음
- 가벼움: 오버헤드가 최소화됨
std::vector의 장점
- 동적 크기: 런타임에 크기 변경 가능
- 수정 가능: 요소 추가, 삭제, 수정 가능
- 풍부한 API: 다양한 멤버 함수 제공
선택 가이드
// initializer_list가 적합한 경우
class Point {
public:
Point(std::initializer_list<double> coords) {
// 좌표 초기화 (읽기 전용)
}
};
// vector가 적합한 경우
class DataProcessor {
public:
void processData(std::vector<int> data) {
// 데이터 수정 및 처리
data.push_back(42);
}
};
템플릿과 함께 사용
템플릿 함수
template<typename T>
T sum(std::initializer_list<T> values) {
T total = T{};
for (const T& value : values) {
total += value;
}
return total;
}
int main() {
int result1 = sum({1, 2, 3, 4, 5}); // 15
double result2 = sum({1.1, 2.2, 3.3}); // 6.6
return 0;
}
가변 템플릿과의 비교
// initializer_list 사용
void printValues(std::initializer_list<int> values) {
for (int val : values) {
std::cout << val << " ";
}
}
// 가변 템플릿 사용
template<typename... Args>
void printValues(Args... args) {
((std::cout << args << " "), ...);
}
int main() {
printValues({1, 2, 3, 4, 5}); // initializer_list
printValues(1, 2, 3, 4, 5); // 가변 템플릿
return 0;
}
한계와 주의사항
1. empty() 메서드 없음
void processList(std::initializer_list<int> list) {
// 틀림: list.empty()
// 맞음: list.size() == 0
if (list.size() == 0) {
std::cout << "Empty list" << std::endl;
}
}
2. std::make_unique와의 호환성 문제
class MyClass {
public:
MyClass(std::initializer_list<int> list) {}
};
// 템플릿 타입 추론 실패
// auto obj = std::make_unique<MyClass>({1, 2, 3});
// 해결책 1: 명시적 타입 지정
auto obj1 = std::make_unique<MyClass>(std::initializer_list<int>{1, 2, 3});
// 해결책 2: 직접 new 사용
auto obj2 = std::unique_ptr<MyClass>(new MyClass{1, 2, 3});
// 해결책 3: std::vector 사용
class MyClassVector {
public:
MyClassVector(const std::vector<int>& vec) {}
};
auto obj3 = std::make_unique<MyClassVector>(std::vector<int>{1, 2, 3});
3. 수명 주의
std::initializer_list<int> getBadList() {
return {1, 2, 3}; // 미정의 동작! 임시 객체의 수명
}
std::vector<int> getGoodList() {
return {1, 2, 3}; // 안전: vector로 복사됨
}
실용적인 예제
1. 수학 함수들
class MathUtils {
public:
static double average(std::initializer_list<double> values) {
if (values.size() == 0) return 0.0;
double sum = 0.0;
for (double val : values) {
sum += val;
}
return sum / values.size();
}
static double max(std::initializer_list<double> values) {
if (values.size() == 0) throw std::invalid_argument("Empty list");
double maxVal = *values.begin();
for (double val : values) {
if (val > maxVal) maxVal = val;
}
return maxVal;
}
};
2. 설정 클래스
class Config {
private:
std::map<std::string, std::string> settings;
public:
Config(std::initializer_list<std::pair<std::string, std::string>> init) {
for (const auto& pair : init) {
settings[pair.first] = pair.second;
}
}
std::string get(const std::string& key) const {
auto it = settings.find(key);
return it != settings.end() ? it->second : "";
}
};
int main() {
Config config{
{"host", "localhost"},
{"port", "8080"},
{"debug", "true"}
};
std::cout << "Host: " << config.get("host") << std::endl;
return 0;
}
3. 그래픽스 객체
class Color {
private:
float r, g, b, a;
public:
Color(std::initializer_list<float> components) {
auto it = components.begin();
r = (it != components.end()) ? *it++ : 0.0f;
g = (it != components.end()) ? *it++ : 0.0f;
b = (it != components.end()) ? *it++ : 0.0f;
a = (it != components.end()) ? *it++ : 1.0f;
}
void print() const {
std::cout << "RGBA(" << r << ", " << g << ", " << b << ", " << a << ")" << std::endl;
}
};
int main() {
Color red{1.0f, 0.0f, 0.0f};
Color semiTransparentBlue{0.0f, 0.0f, 1.0f, 0.5f};
red.print();
semiTransparentBlue.print();
return 0;
}
성능 고려사항
컴파일 타임 최적화
// 컴파일러가 최적화할 수 있는 경우
void processStatic() {
static const std::initializer_list<int> data{1, 2, 3, 4, 5};
// 컴파일 타임에 데이터가 상수로 처리됨
}
// 런타임 처리가 필요한 경우
void processDynamic() {
std::vector<int> data{1, 2, 3, 4, 5};
// 동적 메모리 할당 및 복사 발생
}
메모리 사용량
std::initializer_list: 포인터 + 크기 (보통 16바이트)std::vector: 포인터 + 크기 + 용량 + 동적 할당
모범 사례
1. 적절한 사용 시나리오
- 생성자 초기화: 객체 생성 시 여러 값 전달
- 설정 파라미터: 읽기 전용 설정 값 전달
- 수학적 연산: 여러 숫자를 입력으로 받는 함수
2. 피해야 할 경우
- 데이터 수정 필요:
std::vector사용 - 동적 크기 변경:
std::vector사용 - 복잡한 템플릿 추론: 명시적 타입 지정이 필요한 경우
3. 코드 스타일
// 좋은 예시
class Point {
public:
Point(std::initializer_list<double> coords) :
x(coords.size() > 0 ? *coords.begin() : 0.0),
y(coords.size() > 1 ? *(coords.begin() + 1) : 0.0) {}
private:
double x, y;
};
// 나쁜 예시
class BadPoint {
public:
BadPoint(std::initializer_list<double> coords) {
// 경계 검사 없이 접근 - 위험
x = *coords.begin();
y = *(coords.begin() + 1);
}
private:
double x, y;
};
결론
std::initializer_list는 C++에서 중괄호 초기화 문법을 활용하여 코드를 더 직관적이고 간결하게 만드는 강력한 도구입니다. 특히 생성자와 설정 함수에서 여러 값을 전달할 때 유용하지만, 템플릿 타입 추론의 한계와 수정 불가능한 특성을 이해하고 적절한 상황에서 사용하는 것이 중요합니다.
std::make_unique와의 호환성 문제와 같은 실용적인 제한들을 인지하고, 필요할 때는 std::vector와 같은 대안을 고려하는 것이 좋은 프로그래밍 습관입니다.
'개발 > C++ (98,03,11,14,17,20,23)' 카테고리의 다른 글
| Classic C++ : 정적 멤버 배열 초기화 (0) | 2025.11.17 |
|---|---|
| Classic C++ : 클래스 멤버 배열 초기화 (1) | 2025.11.16 |
| Classic C++ : 1인수 생성자 (0) | 2025.11.15 |
| Classic C++ : 기본 생성자와 2인수 생성자 (0) | 2025.11.14 |
| Classic C++ : 생성자(constructor)와 소멸자(destructor) (0) | 2025.11.02 |
| Before Classic C++ : 레퍼런스(&)와 포인터(*) (0) | 2025.11.01 |
| Before Classic C++ : struct (1) | 2025.10.31 |
| Before Classic C++ : Preprocessor (0) | 2025.10.30 |