32. 빈 기반 클래스 최적화(Empty Base Optimization, EBO)
난이도 중내 리뷰 · C++
해설 · C++
해설 — 빈 기반 클래스 최적화(Empty Base Optimization, EBO)
난이도: 중상
요약
sizeof(Empty)==1, sizeof(EmptyMember)==8, sizeof(EmptyBase)==4, sizeof(TwoEmptyMembers)==8. 빈 타입을 멤버로 담으면 "완전한 객체는 크기가 최소 1"이라는 규칙 때문에 최소 1바이트(+정렬 패딩)를 차지하지만, 기반 클래스로 담으면 EBO 로 그 1바이트가 사라져 파생 객체 크기에 기여하지 않는다. 이것이 표준 라이브러리가 상태 없는 allocator·deleter·comparator 를 멤버가 아니라 상속으로 저장하는 이유다.
핵심 개념
- 완전 객체의 최소 크기 1: 서로 다른 두 객체는 서로 다른 주소를 가져야 한다(주소 유일성). 크기가 0이면 배열 원소들이 같은 주소가 되어 이 규칙이 깨지므로, 완전(complete) 객체는 크기가 최소 1로 강제된다. 그래서
sizeof(Empty)==1. - EBO(Empty Base Optimization): 기반 클래스 부분(base subobject)은 완전 객체가 아니라 **하위 객체(subobject)**다. 하위 객체에는 "최소 크기 1" 규칙이 적용되지 않으므로, 빈 기반 클래스는 파생 클래스 안에서 0바이트를 차지할 수 있다. 표준이 허용하는 최적화이며 주류 ABI(Itanium 등)가 구현한다.
단계별 내부 동작
sizeof(Empty)==1: 완전 객체 최소 크기 규칙.EmptyMember { Empty e; int value; }→ 8:e가 완전 멤버라 1바이트를 차지하고,int(4, 정렬 4) 앞에 3바이트 패딩이 들어가1 + 3(pad) + 4 = 8.EmptyBase : Empty { int value; }→ 4: EBO 로Empty기반 부분이 0바이트.int하나만 남아 4.TwoEmptyMembers { Empty a; Empty b; int value; }→ 8:a와b는 서로 다른 주소를 가져야 하므로 각각 1바이트(합쳐서 접거나 겹칠 수 없다) →1 + 1 + 2(pad) + 4 = 8. 즉 빈 멤버가 둘이면 EBO 로도 접히지 않는다(멤버 경로에는 EBO 가 없다).
EBO 성립 조건과 함정
- 같은 타입의 빈 기반을 두 번 상속하면(같은
Empty를 두 base 로) 두 base subobject 가 서로 다른 주소를 가져야 하므로 하나는 0으로 접히지 못한다. 그래서 라이브러리는 보통 하나만 상속하거나 태그로 타입을 구분한다. - 빈 타입이라도 가상 함수가 있으면(vptr) 더 이상 비어있지 않다(크기 ≥ 포인터).
- C++20 의 **
[[no_unique_address]]**는 멤버 경로에서도 EBO 유사 효과를 준다:[[no_unique_address]] Empty e;로 선언하면 빈 멤버가 0바이트가 될 수 있다(단 서로 다른 빈 멤버가 겹치지 않게 하는 규칙은 유지). 이 문제는 C++17 기준이라 상속 기법을 다룬다.
라이브러리 연결
std::vector<T, Alloc>는 대부분 상태 없는Alloc(예:std::allocator)을 EBO 로 저장해 벡터 크기에 할당자가 기여하지 않게 한다(보통 포인터 3개 크기 유지).std::unique_ptr<T, Deleter>도 상태 없는 deleter 를 EBO 로 접어sizeof == sizeof(T*)를 달성한다(문제집 cpp_internals 의 unique_ptr 커스텀 삭제자 문제와 연결).std::tuple은 빈 요소들을 EBO 로 접어 공간을 절약한다.
흔한 오해·함정
- "빈 클래스는 크기가 0"이라는 오해 — 완전 객체는 1이다.
- "상속이든 멤버든 같다"는 오해 — 크기 최적화 관점에서 상속(EBO)과 멤버는 다르다.
- 같은 빈 타입을 여러 base 로 상속하면 EBO 가 부분적으로만 적용된다는 점.
면접 포인트
- 완전 객체 최소 크기 1 규칙의 이유(주소 유일성)와 subobject 에는 왜 적용되지 않는지.
- EBO 가 표준 라이브러리(allocator/deleter/comparator) 설계에 쓰이는 방식과
[[no_unique_address]](C++20). - 빈 멤버 둘, 가상 함수 존재, 동일 빈 기반 중복 상속 등 EBO 가 깨지는 경계.