C++ Empty Class Optimization

C++ ECO 是一种对于空类型的内存布局优化策略. 在 C++ 中不允许存在大小为零的类型, 即便是空类也会占有一个字节的大小, 像 void 和没有定义的类型称为「非完备类型」(Incomplete Type).

​ 这带来一个问题, 如果将空类作为成员变量的类型, 则每个成员都会占用至少一个字节的大小:

struct NE1 {
    E1 e1;
    E2 e2;
};

int main() {
    printf("%zd\n", sizeof(NE1)); // 2
}

大多数情况下, 还会因为内存对齐的原因, 导致所占用的空间还要大于一个字节:

struct NE2 {
    int i;
    E1 e1;
    E2 e2;
};

int main() {
  printf("%zd\n", sizeof(NE2)); // 8
}

为了解决这个问题, 可以使用 ECO 优化, 即 Empty Base Optimization. 原理在于将空类作为另一个类型的基类时, 该基类将不会占用多余的空间.

struct BE1: E1, E2 {};
struct BE2: E1, E2 {
    int i;
};

int main() {
    printf("%zd\n", sizeof(BE1)); // 1
    printf("%zd\n", sizeof(BE2)); // 4
}

​ 基于此特性, 可以用来实现一些复合模板类, 因为其类型参数可能为任意类型, 也可能为空类, 适合进行 ECO 优化. 例如 boost::compressed_pair 就使用了ECO.

​ 基于 ECO 设计一个 compressed_pair:

namespace evo {

template <typename T1, typename T2>
struct compressed_pair;

template <typename T, size_t I, bool CanBeEmptyBase = 
    std::is_empty_v<T> && !std::is_final_v<T>>
struct compressed_pair_elem {
    typedef T& reference;
    typedef T const& const_reference;

    template <typename U>
    requires (!evo::is_same_v<compressed_pair_elem, evo::decay<U>>)
    explicit compressed_pair_elem(U&& u):
        value(evo::forward<U>(u)) {}

    reference get() noexcept {
        return this->value;
    }

    const_reference get() const noexcept {
        return this->value;
    }

private:
    T value;
};

// Empty Base Optimization
// A empty, non-final type should be inherited instead of 
// put inside the compressed_pair_elem.
template <typename T, size_t I>
struct compressed_pair_elem<T, I, true>: public T {
    typedef T value_type;
    typedef T& reference;
    typedef T const& const_reference;

    constexpr explicit compressed_pair_elem() = default;

    template <typename U>
    requires (!evo::is_same_v<compressed_pair_elem, evo::decay<U>>)
    explicit compressed_pair_elem(U&& u):
        value_type(evo::forward<U>(u)) {}

    reference get() noexcept {
        return *this; // implicit conversion to the base reference.
    }

    const_reference get() const noexcept {
        return *this;
    }
};

template <typename T1, typename T2>
struct compressed_pair: 
    compressed_pair_elem<T1, 1>,
    compressed_pair_elem<T2, 2> 
{
    static_assert((!is_same<T1, T2>::value),
            "compressed_pair cannot be instantiated when T1 and T2 are the same type; ");

    typedef compressed_pair_elem<T1, 1> Base1;
    typedef compressed_pair_elem<T2, 2> Base2;

    explicit compressed_pair()
    requires (
        evo::is_default_constructible_v<T1> &&
        evo::is_default_constructible_v<T2>
    ) = default;

    template <typename U1, typename U2>
    requires (
        evo::is_constructible_v<T1, U1> && 
        evo::is_constructible_v<T2, U2>
    )
    explicit compressed_pair(U1&& u1, U2&& u2):
        Base1(std::forward<U1>(u1)), 
        Base2(std::forward<U2>(u2)) {}

    typename Base1::reference first() noexcept {
        return static_cast<Base1&>(*this).get();
    }
    typename Base1::const_reference first() const noexcept {
        return static_cast<Base1 const&>(*this).get();
    }

    typename Base2::reference second() noexcept {
        return static_cast<Base2&>(*this).get();
    }
    typename Base2::const_reference second() const noexcept {
        return static_cast<Base2 const&>(*this).get();
    }

    void swap(compressed_pair& other) {
        evo::swap(first(), other.first());
        evo::swap(second(), other.second());
    }
};

template <typename T1, typename T2>
void swap(compressed_pair<T1, T2>& p1, compressed_pair<T1, T2>& p2) {
    evo::swap(p1.first(), p2.first());
    evo::swap(p1.second(), p2.second());
}
posted @ 2024-04-13 15:33  kaleidopink  阅读(16)  评论(0编辑  收藏  举报