C++20 Designated Initializers 완벽 가이드 | 명확한 구조체 초기화
이 글의 핵심
C++20 Designated Initializers 완벽 가이드에 대한 실전 가이드입니다. 명확한 구조체 초기화 등을 예제와 함께 상세히 설명합니다.
Designated Initializers란? 왜 필요한가
문제 시나리오: 구조체 초기화의 혼란
문제: 구조체 멤버가 많을 때, 순서대로 값을 나열하면 어떤 값이 어떤 멤버인지 헷갈립니다.
struct Config {
std::string host;
int port;
bool ssl;
int timeout;
int max_connections;
};
int main() {
// 어떤 값이 어떤 멤버인지 불명확
Config cfg = {"localhost", 8080, true, 30, 100};
// port가 8080인가? timeout이 30인가?
}
해결: Designated Initializers(C++20)는 멤버 이름을 명시해 초기화합니다. 코드가 명확해지고, 멤버 순서가 바뀌어도 안전합니다.
int main() {
Config cfg = {
.host = "localhost",
.port = 8080,
.ssl = true,
.timeout = 30,
.max_connections = 100
};
// 명확하고 읽기 쉬움!
}
flowchart LR
subgraph before["일반 초기화"]
b1["Config cfg = val1, val2, val3, val4, val5"]
b2["순서 기억 필요"]
b3["혼란스러움"]
end
subgraph after["Designated Initializers"]
a1["Config cfg = .host=..., .port=..."]
a2["멤버 이름 명시"]
a3["명확함"]
end
b1 --> b2 --> b3
a1 --> a2 --> a3
목차
1. 기본 문법
기본 사용
struct Point {
int x;
int y;
};
int main() {
// C++20 Designated Initializers
Point p1 = {.x = 10, .y = 20};
// 중괄호 생략 가능
Point p2{.x = 30, .y = 40};
std::cout << p1.x << ", " << p1.y << '\n'; // 10, 20
}
일반 초기화와 비교
struct Person {
std::string name;
int age;
std::string city;
};
int main() {
// 일반 초기화 (순서 중요)
Person p1 = {"Alice", 30, "Seoul"};
// Designated Initializers (멤버 이름 명시)
Person p2 = {
.name = "Bob",
.age = 25,
.city = "Busan"
};
}
2. 순서 규칙
선언 순서 유지 필수
C++20에서는 Designated Initializers가 선언 순서를 따라야 합니다 (C와 다름).
struct Data {
int a;
int b;
int c;
};
int main() {
// ✅ 선언 순서대로
Data d1 = {.a = 1, .b = 2, .c = 3}; // OK
// ❌ 순서 위반
// Data d2 = {.b = 2, .a = 1, .c = 3}; // Error in C++20
// ✅ 건너뛰기는 OK
Data d3 = {.a = 1, .c = 3}; // b = 0 (기본값)
}
C vs C++20 차이
| 항목 | C | C++20 |
|---|---|---|
| 순서 | 자유 | 선언 순서 필수 |
| 혼용 | 가능 | 불가 |
| 배열 | 자유 인덱스 | 순차만 |
// C에서는 OK
struct Data { int a, b, c; };
Data d = {.c = 3, .a = 1, .b = 2}; // C: OK, C++20: Error
3. 중첩 구조체
중첩 초기화
struct Address {
std::string city;
std::string street;
int zipcode;
};
struct Employee {
std::string name;
int id;
Address address;
};
int main() {
Employee emp = {
.name = "Alice",
.id = 100,
.address = {
.city = "Seoul",
.street = "Gangnam",
.zipcode = 12345
}
};
std::cout << emp.address.city << '\n'; // Seoul
}
깊은 중첩
struct Location {
double lat;
double lon;
};
struct Address {
std::string street;
Location location;
};
struct Person {
std::string name;
Address address;
};
int main() {
Person p = {
.name = "Bob",
.address = {
.street = "Main St",
.location = {
.lat = 37.5,
.lon = 127.0
}
}
};
}
4. 기본값과 부분 초기화
기본값 제공
struct Settings {
int width = 800;
int height = 600;
bool fullscreen = false;
int fps = 60;
};
int main() {
// 일부만 지정, 나머지는 기본값
Settings s1 = {
.width = 1920,
.height = 1080
};
// fullscreen = false, fps = 60
Settings s2 = {
.fullscreen = true
};
// width = 800, height = 600, fps = 60
}
빈 초기화
struct Point {
int x;
int y;
};
int main() {
Point p = {}; // x = 0, y = 0
}
5. 자주 발생하는 문제와 해결법
문제 1: 순서 위반
증상: error: designator order for field does not match declaration order.
struct Data {
int a;
int b;
int c;
};
int main() {
// ❌ 순서 위반
// Data d = {.b = 2, .a = 1}; // Error
// ✅ 선언 순서
Data d = {.a = 1, .b = 2}; // OK
}
문제 2: 일반 초기화와 혼용
증상: error: cannot mix designated and non-designated initializers.
struct Point {
int x;
int y;
int z;
};
int main() {
// ❌ 혼용 불가
// Point p = {10, .y = 20, .z = 30}; // Error
// ✅ Designated만
Point p1 = {.x = 10, .y = 20, .z = 30}; // OK
// ✅ 일반만
Point p2 = {10, 20, 30}; // OK
}
문제 3: 비 Aggregate 타입
증상: error: designated initializers cannot be used with a non-aggregate type.
원인: 생성자가 있거나, private 멤버가 있는 클래스는 Aggregate가 아닙니다.
// ❌ 생성자 있음
class MyClass {
public:
MyClass(int x) : value(x) {}
int value;
};
// MyClass obj = {.value = 10}; // Error
// ✅ Aggregate (생성자 없음)
struct MyStruct {
int value;
};
MyStruct obj = {.value = 10}; // OK
6. 프로덕션 패턴
패턴 1: 설정 구조체
struct ServerConfig {
std::string host = "0.0.0.0";
int port = 8080;
bool ssl = false;
int timeout_ms = 30000;
int max_connections = 1000;
std::string log_level = "info";
};
ServerConfig load_config() {
// 기본값 + 일부 오버라이드
return ServerConfig{
.host = "localhost",
.port = 9000,
.ssl = true
// 나머지는 기본값
};
}
패턴 2: 빌더 패턴 대체
// Before: 빌더 패턴
class ConfigBuilder {
public:
ConfigBuilder& setHost(std::string h) { host = h; return *this; }
ConfigBuilder& setPort(int p) { port = p; return *this; }
Config build() { return Config{host, port, ...}; }
private:
std::string host;
int port;
};
// After: Designated Initializers (더 간단)
struct Config {
std::string host = "localhost";
int port = 8080;
bool ssl = false;
};
Config cfg = {
.host = "example.com",
.port = 443,
.ssl = true
};
패턴 3: 테스트 데이터
struct TestCase {
std::string name;
int input;
int expected;
};
std::vector<TestCase> tests = {
{.name = "zero", .input = 0, .expected = 0},
{.name = "positive", .input = 5, .expected = 25},
{.name = "negative", .input = -3, .expected = 9}
};
7. 완전한 예제: HTTP 요청 설정
#include <string>
#include <map>
#include <chrono>
#include <iostream>
struct HttpHeaders {
std::string content_type = "application/json";
std::string authorization;
std::string user_agent = "MyApp/1.0";
};
struct HttpRequest {
std::string method = "GET";
std::string url;
HttpHeaders headers;
std::string body;
std::chrono::seconds timeout = std::chrono::seconds(30);
bool follow_redirects = true;
int max_redirects = 5;
};
void send_request(const HttpRequest& req) {
std::cout << req.method << " " << req.url << '\n';
std::cout << "Content-Type: " << req.headers.content_type << '\n';
std::cout << "Timeout: " << req.timeout.count() << "s\n";
}
int main() {
// GET 요청 (기본값 활용)
HttpRequest get_req = {
.url = "https://api.example.com/users"
};
send_request(get_req);
// POST 요청 (일부 오버라이드)
HttpRequest post_req = {
.method = "POST",
.url = "https://api.example.com/users",
.headers = {
.content_type = "application/json",
.authorization = "Bearer token123"
},
.body = R"({"name":"Alice","age":30})",
.timeout = std::chrono::seconds(60)
};
send_request(post_req);
}
정리
| 개념 | 설명 |
|---|---|
| Designated Initializers | 멤버 이름으로 초기화 |
| 문법 | {.member = value} |
| 순서 | 선언 순서 유지 필수 (C++20) |
| 혼용 | 일반 초기화와 혼용 불가 |
| 용도 | 설정, 테스트 데이터, API 파라미터 |
Designated Initializers는 코드 가독성을 높이고, 구조체 초기화를 명확하게 만듭니다.
FAQ
Q1: C와 C++20 차이는?
A: C는 순서가 자유롭고 건너뛰기가 가능하지만, C++20은 선언 순서를 따라야 하고, 일반 초기화와 혼용할 수 없습니다.
Q2: 순서를 지키지 않으면?
A: 컴파일 에러가 납니다. 멤버 선언 순서대로 지정해야 합니다.
Q3: 일부 멤버만 지정하면?
A: 나머지 멤버는 기본값 또는 0으로 초기화됩니다. 기본값을 제공하면 명시하지 않은 멤버는 기본값을 사용합니다.
Q4: 클래스에서도 사용 가능한가요?
A: Aggregate 타입에서만 사용 가능합니다. 생성자가 있거나 private 멤버가 있는 클래스는 불가능합니다.
Q5: 배열은?
A: C++20에서는 배열 Designated Initializers가 제한적입니다. 순차 인덱스만 가능하고, 건너뛰기는 불가능합니다.
Q6: Designated Initializers 학습 리소스는?
A:
- cppreference - Aggregate initialization
- “C++20: The Complete Guide” by Nicolai Josuttis
- C++20 Features
한 줄 요약: Designated Initializers로 구조체 초기화를 명확하게 할 수 있습니다. 다음으로 Aggregate Initialization을 읽어보면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Aggregate Initialization 완벽 가이드 | 집합 초기화
- C++ 균일 초기화 | “Uniform Initialization” 가이드
- C++ List Initialization | “리스트 초기화” 가이드
관련 글
- C++ Aggregate Initialization |
- C++ Aggregate Initialization 완벽 가이드 | 집합 초기화
- C++ Designated Initializers |
- C++ struct vs class |
- C++20 Concepts 완벽 가이드 | 템플릿 제약의 새 시대