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());
}