[2026] C++ Aggregate Initialization | 집합체 초기화 가이드
이 글의 핵심
집합체(aggregate)는 조건을 만족하는 구조체·배열로, 중괄호로 멤버를 직접 채웁니다. C++20 지정 초기화(designated initializers), 기본·값·리스트 초기화와의 차이, 구조체 API 설계 시 흔한 실수를 다룹니다.
집합체 초기화란?
집합체(aggregate) 는 사용자 정의 생성자·비공개 멤버·가상 등이 없는 구조체·배열이며, 중괄호 {}로 한 번에 초기화할 수 있습니다. 리스트 초기화, 값 초기화, 지정 초기화와 함께 보면 초기화 규칙을 정리하기 좋습니다.
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 타입 정의
struct Point {
int x;
int y;
};
Point p = {10, 20}; // 집합체 초기화
왜 필요한가?:
- 간결성: 생성자 없이 멤버를 직접 초기화
- 안전성: 모든 멤버를 명시적으로 초기화
- 가독성: 구조체 정의와 초기화가 명확 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 생성자 방식: 코드가 길어짐
// 타입 정의
struct Point {
int x, y;
Point(int x_, int y_) : x(x_), y(y_) {}
};
Point p(10, 20);
// ✅ 집합체 초기화: 간결
struct Point {
int x, y;
};
Point p = {10, 20};
집합체 조건
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ 집합체
struct Aggregate {
int x;
double y;
};
// ❌ 비집합체
struct NonAggregate {
int x;
private:
int y; // 비공개 멤버
};
집합체 조건 상세: C++17/C++20 기준으로 다음 조건을 모두 만족해야 집합체입니다:
- 배열 또는 클래스 타입 (구조체, 클래스, 공용체)
- 사용자 정의 생성자 없음 (컴파일러 생성 생성자는 OK)
- 비공개/보호 멤버 없음 (모든 멤버가
public) - 가상 함수 없음
- 가상/비공개/보호 기반 클래스 없음 (C++17부터 public 상속은 OK) 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ 집합체 예시
struct Aggregate1 {
int x;
double y;
};
struct Aggregate2 : Aggregate1 { // C++17: public 상속 OK
int z;
};
// ❌ 비집합체 예시
struct NonAggregate1 {
int x;
NonAggregate1() = default; // 사용자 정의 생성자
};
struct NonAggregate2 {
int x;
private:
int y; // 비공개 멤버
};
struct NonAggregate3 {
virtual void func() {} // 가상 함수
};
집합체 판별 방법: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <type_traits>
struct Point { int x, y; };
static_assert(std::is_aggregate_v<Point>); // true
// 사용 예시
template<typename T>
void initIfAggregate(T& obj) {
if constexpr (std::is_aggregate_v<T>) {
obj = {}; // 집합체 초기화
}
}
실전 예시
예시 1: 구조체 초기화
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Person {
std::string name;
int age;
double height;
};
// 집합체 초기화
Person p1 = {"Alice", 30, 165.5};
Person p2{"Bob", 25, 175.0};
// 일부 생략
Person p3 = {"Charlie", 35}; // height는 0.0
Person p4 = {"David"}; // age는 0, height는 0.0
예시 2: 배열 초기화
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5] = {1, 2}; // 나머지는 0
// 2차원 배열
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
예시 3: 중첩 구조체
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Address {
std::string city;
int zipCode;
};
struct Person {
std::string name;
Address address;
};
Person p = {
"Alice",
{"Seoul", 12345}
};
std::cout << p.name << std::endl;
std::cout << p.address.city << std::endl;
예시 4: C++20 지정 초기화
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Config {
int width = 800;
int height = 600;
bool fullscreen = false;
};
// C++20: 지정 초기화
Config cfg = {
.width = 1920,
.height = 1080,
.fullscreen = true
};
다른 초기화 방식과 비교 (집합체 관점)
집합체 초기화는 보통 중괄호 리스트로 이루어지며, 리스트 초기화·값 초기화와 겹칩니다.
| 문법 | 의미 (집합체 S) |
|---|---|
S a; | 기본 초기화 — 지역이면 멤버가 쓰레기일 수 있음 |
S a{}; | 값 초기화 — 멤버를 0·빈 값으로 맞추는 쪽(타입에 따라 0 초기화 포함) |
S a = {1, 2}; | 집합체 초기화(복사 리스트 형태) |
S a{1, 2}; | 집합체 + 리스트 초기화 |
비집합체(사용자 정의 생성자가 있는 클래스)는 S a{...}가 생성자 호출로 가므로, “집합체 초기화”라는 용어는 엄격히는 집합체 타입에만 해당합니다. |
구조체 초기화 실전 패턴
- 한 줄에 의미 부여: API 응답·설정값을
struct로 묶고return { ok, msg };처럼 쓰면 필드 이름이 문서 역할을 합니다. - 기본 멤버 초기화 +
{}: C++11 이후 멤버에= 0또는= {}를 두고, 전체는Config c{}로 통일하면 “어떤 필드도 빠뜨리지 않았는가”를 코드 리뷰하기 쉽습니다. - 중첩: 바깥 구조체 초기화 시 안쪽은 또 다른 집합체로
{...}를 한 단계 더 두면 됩니다(이미 예시 3).
C++20 지정 초기화(designated initializers) 심화
C++20에서는 C99 스타일의 .멤버 = 값 문법이 집합체 초기화에 들어옵니다. 선언 순서와 동일한 순서로만 써야 하며, 건너뛰면 생략된 멤버는 값 초기화됩니다.
struct Point { int x; int y; int z = 0; };
Point p = {.x = 1, .y = 2}; // z는 기본값 0 또는 값 초기화 규칙
// Point q = {.y = 2, .x = 1}; // 순서 어긋남 → C++20에서 ill-formed
장점
- 필드 의미가 이름으로 드러나 큰
struct에서 실수(인자 순서 바꿈)가 줄어듭니다. - 리스트 초기화와 같이 쓰면 가독성이 좋아집니다. 주의
- C와의 호환: C에서는 지정 초기화가 더 유연한 부분이 있었으나, C++20은 위에서 말한 순서 제약이 엄격합니다.
- 비집합체에서는 집합 규칙 밖이므로, 생성자를 쓰는 타입은 생성자 인자로 설계하는 편이 맞습니다.
기본값
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
struct Data {
int x = 10; // 기본값
int y = 20;
int z = 30;
};
Data d1 = {}; // {10, 20, 30}
Data d2 = {100}; // {100, 20, 30}
Data d3 = {100, 200}; // {100, 200, 30}
기본값 동작 원리: 집합체 초기화 시 일부 멤버를 생략하면, 생략된 멤버는:
- 멤버 기본값이 있으면 그 값 사용
- 멤버 기본값이 없으면 값 초기화 (0, nullptr, false 등) 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 타입 정의
struct Config {
int width = 800; // 기본값 있음
int height = 600; // 기본값 있음
bool fullscreen; // 기본값 없음
};
Config c1 = {}; // {800, 600, false} (모두 기본값)
Config c2 = {1920}; // {1920, 600, false} (width만 지정)
Config c3 = {1920, 1080}; // {1920, 1080, false}
Config c4 = {1920, 1080, true}; // {1920, 1080, true} (모두 지정)
실무 활용: 옵셔널 파라미터: 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct HttpRequest {
std::string url;
std::string method = "GET"; // 기본값
int timeout = 30; // 기본값
bool followRedirects = true; // 기본값
};
// 필수 파라미터만 지정
HttpRequest req1 = {"https://example.com"};
// {url: "https://example.com", method: "GET", timeout: 30, followRedirects: true}
// 일부 파라미터 지정
HttpRequest req2 = {"https://example.com", "POST", 60};
// {url: "https://example.com", method: "POST", timeout: 60, followRedirects: true}
자주 발생하는 문제
문제 1: 순서
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Point {
int x;
int y;
};
// ❌ 순서 바뀜
// Point p = {.y = 20, .x = 10}; // C++20에서 에러
// ✅ 순서 유지
Point p = {.x = 10, .y = 20};
문제 2: 생성자 있는 클래스
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 비집합체
struct NonAggregate {
int x;
NonAggregate(int v) : x(v) {}
};
// NonAggregate obj = {10}; // 에러
// ✅ 생성자 호출
NonAggregate obj(10);
문제 3: 상속
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// C++17: 상속된 집합체도 가능
struct Base {
int x;
};
struct Derived : Base {
int y;
};
Derived d = {{10}, 20}; // Base{10}, y=20
문제 4: 배열 크기
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ 크기 명시
int arr1[5] = {1, 2, 3};
// ✅ 크기 추론
int arr2[] = {1, 2, 3}; // 크기 3
// ❌ 초과
// int arr3[2] = {1, 2, 3}; // 에러
초기화 방식 비교
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Point {
int x, y;
};
// 집합체 초기화
Point p1 = {10, 20};
Point p2{10, 20};
// 생성자 호출 (생성자 있으면)
Point p3(10, 20);
// 기본 초기화
Point p4; // x, y는 쓰레기 값
초기화 방법 비교표:
| 방법 | 문법 | 집합체 | 비집합체 | 특징 |
|---|---|---|---|---|
| 집합체 초기화 | Point p = {10, 20}; | ✅ | ❌ | 멤버 직접 초기화 |
| 리스트 초기화 | Point p{10, 20}; | ✅ | ✅ | 좁히기 방지 |
| 직접 초기화 | Point p(10, 20); | ❌ | ✅ | 생성자 호출 |
| 기본 초기화 | Point p; | ⚠️ | ⚠️ | 쓰레기 값 가능 |
| 값 초기화 | Point p{}; | ✅ | ✅ | 0 또는 기본값 |
| 실무 권장: | ||||
| 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다. |
// ✅ 집합체: 중괄호 초기화
struct Config {
int width, height;
};
Config cfg = {800, 600};
// ✅ 비집합체: 생성자 호출
class Widget {
int value_;
public:
Widget(int v) : value_(v) {}
};
Widget w(10);
// ✅ 안전한 초기화: 값 초기화
Config cfg2{}; // {0, 0}
실무 패턴
패턴 1: 설정 구조체
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct ServerConfig {
std::string host = "localhost";
int port = 8080;
int maxConnections = 100;
bool enableLogging = true;
};
// 기본값 사용
ServerConfig cfg1 = {};
// 일부만 변경
ServerConfig cfg2 = {"0.0.0.0", 3000};
// C++20: 지정 초기화
ServerConfig cfg3 = {
.host = "192.168.1.1",
.port = 9000
};
패턴 2: 반환값 최적화
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Result {
bool success;
std::string message;
int errorCode;
};
Result processData(const std::string& data) {
if (data.empty()) {
return {false, "Empty data", 1};
}
// 처리 로직
return {true, "Success", 0};
}
패턴 3: 테스트 데이터
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct TestCase {
std::string input;
std::string expected;
bool shouldPass;
};
std::vector<TestCase> tests = {
{"hello", "HELLO", true},
{"world", "WORLD", true},
{"", "", false}
};
흔한 실수와 함정 (추가)
std::string이 있는 집합체를{0}만으로…
멤버가std::string이면 전부 0으로 두려면 각 멤버 규칙에 맞는 값 초기화가 필요합니다.S{}가 일반적으로 안전합니다.memset으로 0 채우기는 절대 하지 마세요.- 지정 초기화와 순서
선언 순서와 다르게 쓰면 C++20에서 에러입니다. 리팩터링으로 멤버 순서를 바꾸면 지정 초기화 목록도 함께 고쳐야 합니다. - 집합체가 아니게 된 뒤
virtual이나 사용자 정의 생성자를 추가하면 갑자기T a = {1,2,3}가 깨지거나 생성자 경로로 바뀝니다. 공개 API의struct는 변경 시 영향 범위를 넓게 봐야 합니다.
FAQ
Q1: 집합체 조건은 무엇인가요?
A:
- 모든 멤버가
public - 사용자 정의 생성자 없음
- 가상 함수 없음
- C++17부터 public 상속 허용
Q2: 일부 멤버를 생략할 수 있나요?
A: 가능합니다. 생략된 멤버는 멤버 기본값 또는 값 초기화(0, nullptr 등)됩니다.
struct Point { int x, y, z = 100; };
Point p = {10}; // {10, 0, 100}
Q3: 초기화 순서는?
A: 선언 순서대로 초기화됩니다. C++20 지정 초기화도 선언 순서를 따라야 합니다.
Q4: C++20에서 어떤 변화가 있나요?
A: 지정 초기화(designated initializers) 지원이 추가되었습니다.
struct Point { int x, y; };
Point p = {.x = 10, .y = 20}; // C++20
Q5: 배열 크기는 어떻게 결정되나요?
A:
- 명시적:
int arr[5] = {1, 2, 3};(크기 5) - 추론:
int arr[] = {1, 2, 3};(크기 3)
Q6: 중첩 구조체는 어떻게 초기화하나요?
A: 중괄호를 중첩하여 초기화합니다.
struct Inner { int a, b; };
struct Outer { Inner inner; int c; };
Outer o = {{10, 20}, 30}; // inner={10, 20}, c=30
Q7: 집합체 초기화 학습 리소스는?
A:
- “C++ Primer” by Lippman, Lajoie, Moo
- “Effective C++” by Scott Meyers
- cppreference.com - Aggregate initialization 관련 글: 리스트 초기화, 값 초기화, 지정 초기화, 기본 초기화. 한 줄 요약: 집합체 초기화는 중괄호로 구조체나 배열의 멤버를 직접 초기화하는 간결한 방법입니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ List Initialization | “리스트 초기화” 가이드
- C++ Value Initialization | “값 초기화” 가이드
- C++ Designated Initializers | “지정 초기화” 가이드
- C++ Default Initialization | “기본 초기화” 가이드