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());
}
WELCOME TO THE MACHINE