본문으로 바로가기

EBCO와 compressed_pair

category Project/STL 구현 2022. 6. 27. 21:38

EBCO와 compressed_pair

STL에서는 empty 클래스를 많이 사용하게 된다. 이전 게시글에서 언급한 대로 empty 클래스라고 하더라도 반드시 1byte의 용량을 가지게 된다. 하지만 상속을 통하면 최적화 혜택을 받을 수 있는데 이를 EBCO(Empty Base Class Optimization)라 한다. 이번 글에서는 EBCO와 EBCO를 활용하여 최적화하는 기법인 Compressed_pair를 다룬다.

 

EBCO(Empty Base Class Optimization)

Empty 클래스가 용량을 차지하는 이유는 1byte의 용량이라도 가져야 메모리 어딘가에 적재할 수 있기 때문이다. 하지만 Empty가 아닌 클래스에서 Empty 클래스를 상속받게 된다면 상속 받은 클래스의 데이터로 메모리에 적재할 수 있기 때문에 굳이 부모인 Empty 객체를 위한 공간이 필요 없게 된다.

 

아래의 코드는 Empty class 인 clss A를 Empty가 아닌 클래스에서 포함한 경우(B)와 상속 받은 경우(C)를 나타낸 것이다.

class A{
    void foo(){}
}; // size : 1

class B{
    A a; // size: 1
    int num; // size: 4
}; // size: 8

class C : public A{
    int num; // size: 4
}; // size: 4
  • B에서는 객체 a의 공간을 나타내기 위해서 1바이트의 공간이 필요하다. 따라서 class B는 총 8 바이트가 된다.(5가 아니라 8인 이유는 object alignment 때문이며 자세한 내용은 이 글을 참고할것.)
  • C에서는 A 클래스를 위한 별도의 공간이 필요 없기 때문에 4바이트의 공간만 요구된다.

이렇게 Empty 클래스를 상속받는 경우 Empty클래스의 기능을 모두 사용하면서 불필요한 공간 낭비를 줄일 수 있게 된다. 이를 EBCO(Empty Base Class Optimization)라고 한다.

 

compressed_pair

STL에서는 이러한 성질을 활용하여서 Empty 클래스 객체 생성으로 인한 공간 낭비를 줄이는 기법들을 활용한다. 예를 들면 다음과 같은 경우가 있다.

 

  • container의 allocator(Empty class)를 가질 때
  • unique_ptr의 삭제 함수(함수 객체)를 가질 때

 

allocator는 Empty class인데 allocator를 가지기 위해서 별도의 메모리 공간을 사용한다면 낭비가 발생한다. allocator를 가지고 있지만 메모리 공간은 차지하지 않도록 EBCO를 사용할 수 있다. unique_ptr를 생성할 경우 사용자로부터 삭제 함수를 지정받을 수 있다. 삭제 함수는 상태를 가지지 않는 함수 객체이기 때문에 empty 클래스이다. 그럼에도 별도의 메모리 공간을 가지게 된다면 불필요한 공간 낭비가 발생하고, 특히 unique_ptr은 오버헤드가 발생하지 않아야 하기 때문에 별도의 함수 객체를 변수로 가질 수 없다. 이런 문제도 EBCO 기법을 활용한 compressed_pair로 해결할 수 있다.

// T1 이 empty 클래스인 경우
template<typename T1, typename T2>
class compressed_pair : public T1
{
    T2 second;

public:
    const T1& getFirst() { return *this; }
    const T2& getSecond() { return second; }
};

위 모형은 가장 단순화한 compressed_pair의 모습이다. T1이 만약 empty 클래스라면 상속을 통해서 EBCO 혜택을 받을 수 있다. 그리고 상속관계이기 때문에 T1 객체의 주소는 this의 주소와 같다. 따라서 *this를 반환하는 것으로 T1 객체에 접근할 수 있다.

  • container의 경우 empty 클래스인 allocator를 T1에 넣고, 실 데이터를 T2에 넣는 방식으로 활용할 수 있다.
  • unique_ptr의 경우에는 T1에 empty 클래스인 삭제자 함수를 넣고, T2에 실 포인터 값을 넣는 방식으로 활용할 수 있다.

만약 compressed_pair의 구현을 깊이있게살펴보고 싶다면 boost의 compressed_pair를 참고하자.