빈 기본 클래스 최적화
빈 기본 클래스 최적화 (EBO)
빈(empty) 기본 클래스 하위 객체의 크기를 0으로 만들 수 있게 해주는 최적화예요. "빈 클래스를 상속받았는데 그 기본 클래스가 메모리를 차지하지 않게" 하고 싶을 때가 바로 이 최적화가 필요한 순간이에요.
출처: cppreference
본문
설명
어떤 객체나 멤버 하위 객체의 크기는, 그 타입이 빈 클래스 타입(비정적 데이터 멤버가 없는 class나 struct)이더라도 적어도 1이어야 해요. (단, [[no_unique_address]]를 쓰면 예외예요 — C++20 이후) 그래야 같은 타입의 서로 다른 객체 주소가 항상 서로 다르다는 것을 보장할 수 있거든요.
하지만 기본 클래스 하위 객체는 그런 제약이 없어서, 객체 배치에서 완전히 없애버릴 수 있어요.
struct Base {}; // empty class
struct Derived1 : Base
{
int i;
};
int main()
{
// the size of any object of empty class type is at least 1
static_assert(sizeof(Base) >= 1);
// empty base optimization applies
static_assert(sizeof(Derived1) == sizeof(int));
}
빈 기본 클래스 중 하나가 첫 번째 비정적 데이터 멤버의 타입 자체이거나 그 타입의 기본 클래스라면, 빈 기본 클래스 최적화는 금지돼요. 같은 타입의 두 기본 하위 객체는 가장 파생된 타입의 객체 표현 안에서 서로 다른 주소를 가져야 하기 때문이에요.
이런 상황의 전형적인 예가 std::reverse_iterator의 단순한 구현이에요. 이 클래스는 (빈 기본 클래스인 std::iterator에서 파생되고) 내부 반복자(역시 std::iterator에서 파생됨)를 첫 번째 비정적 데이터 멤버로 갖고 있죠.
struct Base {}; // empty class
struct Derived1 : Base
{
int i;
};
struct Derived2 : Base
{
Base c; // Base, occupies 1 byte, followed by padding for i
int i;
};
struct Derived3 : Base
{
Derived1 c; // derived from Base, occupies sizeof(int) bytes
int i;
};
int main()
{
// empty base optimization does not apply,
// base occupies 1 byte, Base member occupies 1 byte
// followed by 2 bytes of padding to satisfy int alignment requirements
static_assert(sizeof(Derived2) == 2*sizeof(int));
// empty base optimization does not apply,
// base takes up at least 1 byte plus the padding
// to satisfy alignment requirement of the first member (whose
// alignment is the same as int)
static_assert(sizeof(Derived3) == 3*sizeof(int));
}
빈 기본 클래스 최적화는 **표준 배치 타입(StandardLayoutType)**에 필수로 요구돼요. 표준 배치 객체를 reinterpret_cast로 변환한 포인터가 그 객체의 첫 번째 멤버를 가리켜야 한다는 요구 사항을 유지해야 하기 때문이에요. 그래서 표준 배치 타입의 요구 사항에 "모든 비정적 데이터 멤버가 같은 클래스에 선언되어야 한다"는 조건과 "첫 번째 비정적 데이터 멤버와 같은 타입의 기본 클래스를 갖지 않아야 한다"는 조건이 들어가는 거예요. (C++11 이후)
빈 멤버 하위 객체도 [[no_unique_address]] 속성을 쓰면 빈 기본 클래스처럼 최적화되어 빠질 수 있어요. 그런 멤버의 주소를 취하면 같은 객체의 다른 멤버 주소와 같을 수 있어요.
struct Empty {}; // empty class
struct X
{
int i;
[[no_unique_address]] Empty e;
};
int main()
{
// the size of any object of empty class type is at least 1
static_assert(sizeof(Empty) >= 1);
// empty member optimized out:
static_assert(sizeof(X) == sizeof(int));
}
(C++20 이후)
Notes
빈 기본 클래스 최적화는 할당자(allocator)를 인식하는 표준 라이브러리 클래스(std::vector, std::function, std::shared_ptr 등)에서 흔히 사용돼요. 할당자가 상태가 없는(stateless) 경우 그 할당자 멤버 때문에 추가 저장 공간을 차지하지 않도록요. 이는 필요한 데이터 멤버 중 하나(예를 들어 vector의 begin, end, capacity 포인터)를 할당자와 함께 boost::compressed_pair에 해당하는 방식으로 저장해서 이루어져요.
MSVC에서는 빈 기본 클래스 최적화가 표준 요구 사항을 완전히 따르지 않아요 (빈 기본 클래스 최적화(EBO)가 MSVC에서 작동하지 않는 이유는 무엇인가요? 참고).
참고 문헌
- C++23 표준 (ISO/IEC 14882:2024): 7.6.10 Equality operators [expr.eq], 7.6.2.5 Sizeof [expr.sizeof], 11 Classes [class], 11.4 Class members [class.mem]
- C++20 표준 (ISO/IEC 14882:2020): 7.6.10 Equality operators [expr.eq], 7.6.2.4 Sizeof [expr.sizeof], 11 Classes [class], 11.4 Class members [class.mem]
- C++17 표준 (ISO/IEC 14882:2017): 8.10 Equality operators [expr.eq], 8.3.3 Sizeof [expr.sizeof], 12 Classes [class], 12.2 Class members [class.mem]
- C++14 표준 (ISO/IEC 14882:2014): 5.10 Equality operators [expr.eq], 5.3.3 Sizeof [expr.sizeof], 9 Classes [class], 9.2 Class members [class.mem]
- C++11 표준 (ISO/IEC 14882:2011): 5.10 Equality operators [expr.eq] (p: 2), 5.3.3 Sizeof [expr.sizeof] (p: 2), 9 Classes [class] (p: 4,7), 9.2 Class members [class.mem] (p: 20)
- C++98 표준 (ISO/IEC 14882:1998): 5.10 Equality operators [expr.eq] (p: 2), 5.3.3 Sizeof [expr.sizeof] (p: 2), 9 Classes [class] (p: 3)
외부 링크
더 알아보기 (Learn more)
- no_unique_address 속성 — 멤버 하위 객체가 중복 저장을 피할 수 있게 하는 (C++20) 속성
- 클래스 선언 — 클래스 타입의 기본 개념